鱼C论坛

 找回密码
 立即注册
查看: 50|回复: 4

[作品展示] 用python内置库做的html编译器(有点小bug)

[复制链接]
发表于 前天 21:26 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能^_^

您需要 登录 才可以下载或查看,没有账号?立即注册

x
  1. import tkinter as tk
  2. from tkinter import scrolledtext, filedialog, messagebox
  3. import re
  4. import tempfile
  5. import webbrowser
  6. import os

  7. class HTMLCompiler:
  8.     def __init__(self, root):
  9.         self.root = root
  10.         self.root.title("HTML 编译器")
  11.         self.root.configure(bg='black')
  12.         self.current_file = None
  13.         
  14.         # 创建菜单栏
  15.         self.menu_bar = tk.Menu(root)
  16.         
  17.         # 文件菜单
  18.         self.file_menu = tk.Menu(self.menu_bar, tearoff=0)
  19.         self.file_menu.add_command(label="新建", command=self.new_file, accelerator="Ctrl+N")
  20.         self.file_menu.add_command(label="打开", command=self.open_file, accelerator="Ctrl+O")
  21.         self.file_menu.add_command(label="保存", command=self.save_file, accelerator="Ctrl+S")
  22.         self.file_menu.add_command(label="另存为", command=self.save_as_file)
  23.         self.file_menu.add_separator()
  24.         self.file_menu.add_command(label="退出", command=self.root.quit)
  25.         self.menu_bar.add_cascade(label="文件", menu=self.file_menu)
  26.         
  27.         # 运行菜单
  28.         self.run_menu = tk.Menu(self.menu_bar, tearoff=0)
  29.         self.run_menu.add_command(label="运行HTML", command=self.run_html, accelerator="F5")
  30.         self.menu_bar.add_cascade(label="运行", menu=self.run_menu)
  31.         
  32.         self.root.config(menu=self.menu_bar)
  33.         
  34.         # 创建工具栏
  35.         self.toolbar = tk.Frame(root, bg='#333')
  36.         
  37.         self.new_btn = tk.Button(self.toolbar, text="新建", command=self.new_file, bg='#333', fg='white')
  38.         self.open_btn = tk.Button(self.toolbar, text="打开", command=self.open_file, bg='#333', fg='white')
  39.         self.save_btn = tk.Button(self.toolbar, text="保存", command=self.save_file, bg='#333', fg='white')
  40.         self.run_btn = tk.Button(self.toolbar, text="运行", command=self.run_html, bg='#333', fg='white')
  41.         
  42.         self.new_btn.pack(side=tk.LEFT, padx=2, pady=2)
  43.         self.open_btn.pack(side=tk.LEFT, padx=2, pady=2)
  44.         self.save_btn.pack(side=tk.LEFT, padx=2, pady=2)
  45.         self.run_btn.pack(side=tk.LEFT, padx=2, pady=2)
  46.         
  47.         self.toolbar.pack(fill=tk.X)
  48.         
  49.         # 创建文本编辑器
  50.         self.text_area = scrolledtext.ScrolledText(
  51.             root, wrap=tk.WORD, bg='black', fg='white',
  52.             insertbackground='white', font=('Consolas', 12)
  53.         )
  54.         self.text_area.pack(fill=tk.BOTH, expand=True)
  55.         
  56.         # 绑定事件
  57.         self.text_area.bind('<KeyRelease>', self.on_key_release)
  58.         self.text_area.bind('<Tab>', self.handle_tab)
  59.         self.text_area.bind('<Return>', self.handle_return)
  60.         self.text_area.bind('<KeyPress>', self.handle_key_press)
  61.         
  62.         # 绑定快捷键
  63.         self.root.bind('<Control-n>', lambda event: self.new_file())
  64.         self.root.bind('<Control-o>', lambda event: self.open_file())
  65.         self.root.bind('<Control-s>', lambda event: self.save_file())
  66.         self.root.bind('<F5>', lambda event: self.run_html())
  67.         
  68.         # 自动补全相关
  69.         self.autocomplete_window = None
  70.         self.autocomplete_listbox = None
  71.         self.autocomplete_pos = None
  72.         self.autocomplete_words = [
  73.             '<html>', '</html>', '<head>', '</head>', '<body>', '</body>',
  74.             '<title>', '</title>', '<div>', '</div>', '<p>', '</p>',
  75.             '<h1>', '</h1>', '<h2>', '</h2>', '<h3>', '</h3>',
  76.             '<a>', '</a>', '<img>', '<ul>', '</ul>', '<li>', '</li>',
  77.             '<table>', '</table>', '<tr>', '</tr>', '<td>', '</td>',
  78.             '<style>', '</style>', '<script>', '</script>', '<span>', '</span>',
  79.             '<br>', '<hr>', '<input>', '<button>', '</button>', '<form>', '</form>'
  80.         ]
  81.         
  82.         # 语法高亮颜色配置
  83.         self.tag_colors = {
  84.             'tag': '#569CD6',
  85.             'attribute': '#9CDCFE',
  86.             'value': '#CE9178',
  87.             'comment': '#6A9955',
  88.             'doctype': '#569CD6',
  89.             'string': '#CE9178',
  90.             'symbol': '#D4D4D4'
  91.         }
  92.         
  93.         # 配置标签样式
  94.         for tag, color in self.tag_colors.items():
  95.             self.text_area.tag_config(tag, foreground=color)
  96.         
  97.         # 初始高亮
  98.         self.highlight()
  99.         
  100.         # 新建一个空白文件
  101.         self.new_file()
  102.    
  103.     # 文件操作功能
  104.     def new_file(self, event=None):
  105.         self.text_area.delete('1.0', tk.END)
  106.         self.current_file = None
  107.         self.root.title("HTML 编译器 - 未命名")
  108.    
  109.     def open_file(self, event=None):
  110.         file_path = filedialog.askopenfilename(
  111.             filetypes=[("HTML 文件", "*.html"), ("所有文件", "*.*")]
  112.         )
  113.         if file_path:
  114.             try:
  115.                 with open(file_path, 'r', encoding='utf-8') as file:
  116.                     content = file.read()
  117.                     self.text_area.delete('1.0', tk.END)
  118.                     self.text_area.insert('1.0', content)
  119.                     self.current_file = file_path
  120.                     self.root.title(f"HTML 编译器 - {os.path.basename(file_path)}")
  121.                     self.highlight()
  122.             except Exception as e:
  123.                 messagebox.showerror("错误", f"无法打开文件:\n{str(e)}")
  124.    
  125.     def save_file(self, event=None):
  126.         if self.current_file:
  127.             try:
  128.                 with open(self.current_file, 'w', encoding='utf-8') as file:
  129.                     file.write(self.text_area.get('1.0', tk.END))
  130.                 messagebox.showinfo("保存", "文件保存成功!")
  131.             except Exception as e:
  132.                 messagebox.showerror("错误", f"无法保存文件:\n{str(e)}")
  133.         else:
  134.             self.save_as_file()
  135.    
  136.     def save_as_file(self):
  137.         file_path = filedialog.asksaveasfilename(
  138.             defaultextension=".html",
  139.             filetypes=[("HTML 文件", "*.html"), ("所有文件", "*.*")]
  140.         )
  141.         if file_path:
  142.             try:
  143.                 with open(file_path, 'w', encoding='utf-8') as file:
  144.                     file.write(self.text_area.get('1.0', tk.END))
  145.                 self.current_file = file_path
  146.                 self.root.title(f"HTML 编译器 - {os.path.basename(file_path)}")
  147.                 messagebox.showinfo("保存", "文件保存成功!")
  148.             except Exception as e:
  149.                 messagebox.showerror("错误", f"无法保存文件:\n{str(e)}")
  150.    
  151.     def run_html(self, event=None):
  152.         html_code = self.text_area.get("1.0", tk.END)
  153.         
  154.         if not html_code.strip():
  155.             messagebox.showwarning("警告", "没有内容可运行!")
  156.             return
  157.         
  158.         # 创建临时HTML文件
  159.         with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.html') as f:
  160.             f.write(html_code)
  161.             temp_path = f.name
  162.         
  163.         # 用默认浏览器打开
  164.         try:
  165.             webbrowser.open('file://' + os.path.abspath(temp_path))
  166.         except Exception as e:
  167.             messagebox.showerror("错误", f"无法运行HTML:\n{str(e)}")
  168.    
  169.     # 以下是原有的编辑器功能(自动补全、语法高亮等)
  170.     def on_key_release(self, event):
  171.         self.highlight()
  172.         self.check_autocomplete(event)
  173.    
  174.     def handle_key_press(self, event):
  175.         if event.keysym == 'Escape' and self.autocomplete_window:
  176.             self.autocomplete_window.destroy()
  177.             self.autocomplete_window = None
  178.             return 'break'
  179.         
  180.         if self.autocomplete_window and event.keysym in ('Return', 'Tab', 'Down', 'Up'):
  181.             return self.handle_autocomplete_key(event)
  182.         
  183.         if event.keysym == 'BackSpace':
  184.             self.handle_backspace(event)
  185.    
  186.     def handle_backspace(self, event):
  187.         current_pos = self.text_area.index(tk.INSERT)
  188.         line, col = map(int, current_pos.split('.'))
  189.         line_text = self.text_area.get(f'{line}.0', f'{line}.end')
  190.         
  191.         if col > 0 and line_text[:col].isspace():
  192.             prev_non_space = col - 1
  193.             while prev_non_space >= 0 and line_text[prev_non_space].isspace():
  194.                 prev_non_space -= 1
  195.             
  196.             if prev_non_space == -1:
  197.                 space_count = col
  198.                 if space_count % 4 != 0:
  199.                     new_col = (space_count // 4) * 4
  200.                     self.text_area.delete(f'{line}.{new_col}', current_pos)
  201.                     return 'break'
  202.         return None
  203.    
  204.     def handle_tab(self, event):
  205.         current_pos = self.text_area.index(tk.INSERT)
  206.         line, col = map(int, current_pos.split('.'))
  207.         
  208.         space_count = 4 - (col % 4)
  209.         self.text_area.insert(tk.INSERT, ' ' * space_count)
  210.         return 'break'
  211.    
  212.     def handle_return(self, event):
  213.         current_pos = self.text_area.index(tk.INSERT)
  214.         line, col = map(int, current_pos.split('.'))
  215.         line_text = self.text_area.get(f'{line}.0', f'{line}.end')
  216.         
  217.         indent = 0
  218.         while indent < len(line_text) and line_text[indent] == ' ':
  219.             indent += 1
  220.         
  221.         self.text_area.insert(tk.INSERT, '\n' + ' ' * indent)
  222.         
  223.         if line_text.strip().startswith('<') and not line_text.strip().startswith('</'):
  224.             match = re.match(r'^\s*<([a-zA-Z]+)[^>]*>', line_text)
  225.             if match and not line_text.strip().endswith('/>'):
  226.                 tag = match.group(1)
  227.                 self.text_area.insert(tk.INSERT, f'\n{" " * indent}</{tag}>')
  228.                 self.text_area.mark_set(tk.INSERT, f'{line+1}.{indent+1}')
  229.         
  230.         return 'break'
  231.    
  232.     def check_autocomplete(self, event):
  233.         if self.autocomplete_window:
  234.             self.autocomplete_window.destroy()
  235.             self.autocomplete_window = None
  236.         
  237.         current_pos = self.text_area.index(tk.INSERT)
  238.         line, col = map(int, current_pos.split('.'))
  239.         line_text = self.text_area.get(f'{line}.0', current_pos)
  240.         
  241.         if line_text and (line_text[-1].isalpha() or line_text[-1] == '<' or line_text[-1] == '/'):
  242.             word_start = max(line_text.rfind(' '), line_text.rfind('<'), line_text.rfind('>'), line_text.rfind('"')) + 1
  243.             current_word = line_text[word_start:]
  244.             
  245.             matches = [w for w in self.autocomplete_words if w.startswith(current_word.lower())]
  246.             
  247.             if matches:
  248.                 self.show_autocomplete(matches, current_pos, word_start, col)
  249.    
  250.     def show_autocomplete(self, matches, current_pos, word_start, col):
  251.         line, _ = map(int, current_pos.split('.'))
  252.         
  253.         self.autocomplete_window = tk.Toplevel(self.root)
  254.         self.autocomplete_window.wm_overrideredirect(True)
  255.         self.autocomplete_window.wm_geometry("+%d+%d" % (
  256.             self.root.winfo_rootx() + self.text_area.winfo_x() +
  257.             self.text_area.tk.call(self.text_area._w, 'index', f'{line}.{word_start}',
  258.                                   'xpixels', 'ypixels')[0],
  259.             self.root.winfo_rooty() + self.text_area.winfo_y() +
  260.             self.text_area.tk.call(self.text_area._w, 'index', f'{line}.{word_start}',
  261.                                   'xpixels', 'ypixels')[1] + 20
  262.         ))
  263.         
  264.         self.autocomplete_listbox = tk.Listbox(
  265.             self.autocomplete_window,
  266.             height=min(10, len(matches)),
  267.             bg='#2D2D2D', fg='white',
  268.             selectbackground='#3D3D3D',
  269.             font=('Consolas', 12)
  270.         )
  271.         self.autocomplete_listbox.pack()
  272.         
  273.         for match in matches:
  274.             self.autocomplete_listbox.insert(tk.END, match)
  275.         
  276.         self.autocomplete_listbox.bind('<Return>', self.select_autocomplete)
  277.         self.autocomplete_listbox.bind('<Tab>', self.select_autocomplete)
  278.         self.autocomplete_listbox.bind('<Escape>', lambda e: self.autocomplete_window.destroy())
  279.         self.autocomplete_listbox.bind('<Button-1>', self.select_autocomplete)
  280.         
  281.         self.autocomplete_pos = (line, word_start, col)
  282.         
  283.         self.autocomplete_listbox.focus_set()
  284.         self.autocomplete_listbox.selection_set(0)
  285.    
  286.     def handle_autocomplete_key(self, event):
  287.         if not self.autocomplete_listbox:
  288.             return
  289.         
  290.         selection = self.autocomplete_listbox.curselection()
  291.         
  292.         if event.keysym == 'Return' or event.keysym == 'Tab':
  293.             if selection:
  294.                 self.select_autocomplete(event)
  295.             return 'break'
  296.         elif event.keysym == 'Down':
  297.             if selection and selection[0] < self.autocomplete_listbox.size() - 1:
  298.                 self.autocomplete_listbox.selection_clear(selection[0])
  299.                 self.autocomplete_listbox.selection_set(selection[0] + 1)
  300.             return 'break'
  301.         elif event.keysym == 'Up':
  302.             if selection and selection[0] > 0:
  303.                 self.autocomplete_listbox.selection_clear(selection[0])
  304.                 self.autocomplete_listbox.selection_set(selection[0] - 1)
  305.             return 'break'
  306.         
  307.         return None
  308.    
  309.     def select_autocomplete(self, event):
  310.         if not self.autocomplete_listbox:
  311.             return
  312.         
  313.         selection = self.autocomplete_listbox.curselection()
  314.         if selection:
  315.             selected = self.autocomplete_listbox.get(selection[0])
  316.             line, word_start, col = self.autocomplete_pos
  317.             
  318.             self.text_area.delete(f'{line}.{word_start}', f'{line}.{col}')
  319.             self.text_area.insert(f'{line}.{word_start}', selected)
  320.             
  321.             self.text_area.mark_set(tk.INSERT, f'{line}.{word_start + len(selected)}')
  322.         
  323.         self.autocomplete_window.destroy()
  324.         self.autocomplete_window = None
  325.         self.text_area.focus_set()
  326.         return 'break'
  327.    
  328.     def highlight(self):
  329.         for tag in self.tag_colors.keys():
  330.             self.text_area.tag_remove(tag, '1.0', tk.END)
  331.         
  332.         text = self.text_area.get('1.0', tk.END)
  333.         
  334.         # 高亮注释
  335.         self.highlight_pattern(r'<!--.*?-->', 'comment')
  336.         
  337.         # 高亮DOCTYPE
  338.         self.highlight_pattern(r'<!DOCTYPE.*?>', 'doctype')
  339.         
  340.         # 高亮标签
  341.         self.highlight_pattern(r'<\/?[a-zA-Z]+', 'tag')
  342.         self.highlight_pattern(r'<\/[a-zA-Z]+>', 'tag')
  343.         
  344.         # 高亮属性
  345.         self.highlight_pattern(r'\s[a-zA-Z-]+=', 'attribute')
  346.         
  347.         # 高亮属性值
  348.         self.highlight_pattern(r'"[^"]*"', 'value')
  349.         self.highlight_pattern(r"'[^']*'", 'value')
  350.         
  351.         # 高亮符号
  352.         self.highlight_pattern(r'[<>\/=]', 'symbol')
  353.    
  354.     def highlight_pattern(self, pattern, tag):
  355.         start = '1.0'
  356.         end = tk.END
  357.         
  358.         self.text_area.mark_set('matchStart', start)
  359.         self.text_area.mark_set('matchEnd', start)
  360.         
  361.         count = tk.IntVar()
  362.         while True:
  363.             index = self.text_area.search(
  364.                 pattern, 'matchEnd', end,
  365.                 count=count, regexp=True
  366.             )
  367.             if index == '': break
  368.             
  369.             self.text_area.mark_set('matchStart', index)
  370.             self.text_area.mark_set('matchEnd', f'{index}+{count.get()}c')
  371.             
  372.             self.text_area.tag_add(tag, 'matchStart', 'matchEnd')

  373. if __name__ == '__main__':
  374.     root = tk.Tk()
  375.     root.geometry('800x600')
  376.     app = HTMLCompiler(root)
  377.     root.mainloop()
复制代码

评分

参与人数 2荣誉 +2 鱼币 +3 贡献 +2 收起 理由
小甲鱼的二师兄 + 2 + 2 + 2 鱼C有你更精彩^_^
某一个“天” + 1

查看全部评分

小甲鱼最新课程 -> https://ilovefishc.com
回复

使用道具 举报

发表于 前天 21:27 | 显示全部楼层
我注意到你的HTML编译器代码有几个小问题,主要涉及自动补全功能未完成和部分逻辑错误。以下是修复建议:

### 主要问题修复:
1. **自动补全列表未定义**:  
   需要补全 `self.autocomplete_words` 列表,例如:
  

  1.    self.autocomplete_words = ['<html>', '<head>', '<body>', '<div>', '<p>', '<a>', '<img>', '<script>', '<style>', 'class', 'id', 'href', 'src']
  2.    
复制代码


2. **文件对话框类型缺失**:  
   在 `open_file` 和 `save_as_file` 方法中补全文件类型:
  

  1.    filetypes=[('HTML 文件', '*.html'), ('所有文件', '*.*')]
  2.    
复制代码


3. **回车缩进逻辑错误**:  
   修复 `handle_return` 的缩进计算:
  

  1.    def handle_return(self, event):
  2.        current_pos = self.text_area.index(tk.INSERT)
  3.        line, col = map(int, current_pos.split('.'))
  4.        line_text = self.text_area.get(f'{line}.0', f'{line}.end')
  5.       
  6.        indent = 0
  7.        # 修正:遍历字符而非整个字符串
  8.        while indent < len(line_text) and line_text[indent] == ' ':
  9.            indent += 1
  10.       
  11.        self.text_area.insert(tk.INSERT, '\n' + ' ' * indent)
  12.        return 'break'  # 阻止默认回车行为
  13.    
复制代码


### 其他优化建议:
1. **语法高亮增强**:  
   在 `highlight` 方法(未提供)中建议使用正则表达式匹配标签:
   re.findall(r'<[^>]+>', content)

2. **临时文件清理**:  
   在 `run_html` 中添加延时删除临时文件:
  

  1.    import threading
  2.    import time
  3.    
  4.    def delete_temp_file(path):
  5.        time.sleep(5)  # 延迟5秒确保浏览器加载
  6.        os.remove(path)
  7.    
  8.    # 在webbrowser.open后添加
  9.    threading.Thread(target=delete_temp_file, args=(temp_path,)).start()
  10.    
复制代码


3. **自动补全框架**:  
   补全基础自动补全方法(需自行完善匹配逻辑):
  

  1.    def check_autocomplete(self, event):
  2.        # 获取当前单词逻辑示例
  3.        current_pos = self.text_area.index(tk.INSERT)
  4.        words = self.text_area.get("1.0", current_pos).split()
  5.        if not words:
  6.            return
  7.        last_word = words[-1]
  8.       
  9.        # 简单示例:匹配开头字符
  10.        matches = [w for w in self.autocomplete_words if w.startswith(last_word)]
  11.        if matches:
  12.            self.show_autocomplete(matches, current_pos)
  13.    
复制代码


### 使用注意事项:
1. 确保语法高亮方法 `highlight` 已实现
2. 自动补全功能需要完善位置计算和选择逻辑
3. Windows系统可能需要调整临时文件路径处理

> 提示:你可以先注释掉自动补全相关的绑定事件(self.text_area.bind('<KeyRelease>', self.on_key_release)),优先确保基础功能运行正常。

需要进一步调试某个功能可以告诉我具体问题点,我会提供更详细的解决方案!

以上回复来自 FishC AI 小助理,如未能正确解答您的问题,请继续追问。
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 前天 21:36 | 显示全部楼层
小甲鱼最新课程 -> https://ilovefishc.com
回复

使用道具 举报

发表于 昨天 00:48 | 显示全部楼层
我去,这么狠的吗?
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 昨天 00:52 | 显示全部楼层
看了一下,这段代码其实是用 tkinter 做了一个简单的 HTML 编辑器,能新建、打开、保存、运行 HTML 文件,还带了点语法高亮和自动补全,操作起来比较方便。

下次发帖配点图片那就更好了,加油~


小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备18085999号-1 | 粤公网安备 44051102000585号)

GMT+8, 2025-7-3 09:22

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表