select + range + insertNode+offset

本文介绍了一款富文本编辑器中选区操作的方法,包括如何使用range对象进行节点内容的选择、插入节点等操作,并详细解释了startContainer、endContainer、startOffset、endOffset等属性的作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

self. cmd.range.selectNodeContents(p[0])   建立选区
self.cmd.select()   选中选区
self.cmd.selection([forceRest])  根据当前选中状态重置range对象,forceRest默认值为false,值为true时如果当前没有选中信息,自动选择文档(body编辑区域最后节点之后)的最后位置
insertNode是在当前选区之前插入的

理解这个range关键都在红色的分界线
up 提高一个range

<p>
        <strong id="nstrong"><span>123</span>abc</strong>def
    </p>

        
var range = mainbody_editor.cmd.range;
      var len = doc.getElementById("nstrong").childNodes;  [span, text]
     var up = range.up();

offset就是从初始位置到分界线有多少个节点

12|345|6
红色的竖线是分界线,startoffset 位置前头有2个节点所以是2,endoffset前头有5个节点所以是5
 <strong id="nstrong">|<span>123</span>|abc</strong>def
startoffset就是0  endoffset就是1
 
选区部分 startContainer text   startoffset 0   endContainer text   endffset 3

startContainer的startoffset在开始处,endContainer的endffset在结尾处
up之后  就是找到container的container
选区部分 startContainer span    startoffset 0   endContainer strong   startoffset 2

enlarge之后就是p,相当于找到公共的父节点

在文本节点内部 up enlarge是不变的,对于选区是元素节点是变化的
123
 
setStartAfter 是在一个节点的结束位置之后 比如 text | 这个时候偏移量是1,因为前面只有一个text节点

转载于:https://www.cnblogs.com/znsongshu/p/6079413.html

import tkinter as tk from tkinter import ttk, messagebox, scrolledtext, simpledialog import traceback import sys import os # 添加当前目录到系统路径,确保导入成功 current_dir = os.path.dirname(os.path.abspath(__file__)) sys.path.insert(0, current_dir) try: from fat32_reader import FAT32Writer, FAT32Analyzer except ImportError as e: print(f"导入错误: {e}") print(f"当前目录: {current_dir}") print(f"Python 路径: {sys.path}") # 如果导入失败,显示错误信息并退出 root = tk.Tk() root.withdraw() messagebox.showerror("导入错误", f"无法导入 FAT32 模块: {e}\n请确保 fat32_reader.py 文件存在") sys.exit(1) class FAT32GUI: """FAT32文件系统图形界面""" def __init__(self, fat32_reader): self.reader = fat32_reader self.analyzer = FAT32Analyzer(self.reader) # 创建主窗口 self.root = tk.Tk() self.root.title(f"FAT32 文件系统可视化 - {self.reader.get_volume_label()}") self.root.geometry("1400x900") # 创建主框架 main_frame = tk.Frame(self.root) main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) # 创建顶部控制面板 control_frame = tk.Frame(main_frame) control_frame.pack(fill=tk.X, pady=(0, 10)) # 创建按钮 self.create_btn = tk.Button( control_frame, text="新建文件", command=self.create_file_dialog, width=15 ) self.create_btn.pack(side=tk.LEFT, padx=5) self.refresh_btn = tk.Button( control_frame, text="刷新视图", command=self.refresh_view, width=15 ) self.refresh_btn.pack(side=tk.LEFT, padx=5) # 创建过滤选项 filter_frame = tk.LabelFrame(control_frame, text="显示选项") filter_frame.pack(side=tk.LEFT, padx=20) self.show_hidden_var = tk.BooleanVar(value=False) self.show_system_var = tk.BooleanVar(value=False) self.show_special_var = tk.BooleanVar(value=False) tk.Checkbutton( filter_frame, text="显示隐藏文件", variable=self.show_hidden_var, command=self.refresh_view ).pack(side=tk.LEFT, padx=5) tk.Checkbutton( filter_frame, text="显示系统文件", variable=self.show_system_var, command=self.refresh_view ).pack(side=tk.LEFT, padx=5) tk.Checkbutton( filter_frame, text="显示系统目录", variable=self.show_special_var, command=self.refresh_view ).pack(side=tk.LEFT, padx=5) # 创建主分割面板 main_paned = tk.PanedWindow(main_frame, orient=tk.HORIZONTAL, sashrelief=tk.RAISED, sashwidth=4) main_paned.pack(fill=tk.BOTH, expand=True) # ========== 左侧目录树 ========== left_frame = tk.Frame(main_paned) main_paned.add(left_frame, width=400) tree_frame = tk.LabelFrame(left_frame, text="目录结构") tree_frame.pack(fill=tk.BOTH, expand=True) # 创建树视图 tree_scroll = ttk.Scrollbar(tree_frame, orient="vertical") tree_scroll.pack(side=tk.RIGHT, fill=tk.Y) self.tree = ttk.Treeview( tree_frame, columns=('Name', 'Size', 'Cluster', 'Created', 'Modified'), show='tree headings', yscrollcommand=tree_scroll.set ) tree_scroll.config(command=self.tree.yview) # 配置列 self.tree.heading('#0', text='类型', anchor=tk.W) self.tree.heading('Name', text='文件名', anchor=tk.W) self.tree.heading('Size', text='大小', anchor=tk.W) self.tree.heading('Cluster', text='起始簇', anchor=tk.W) self.tree.heading('Created', text='创建时间', anchor=tk.W) self.tree.heading('Modified', text='修改时间', anchor=tk.W) self.tree.column('#0', width=60, minwidth=60) self.tree.column('Name', width=250, minwidth=150) self.tree.column('Size', width=100, minwidth=80) self.tree.column('Cluster', width=80, minwidth=60) self.tree.column('Created', width=180, minwidth=120) self.tree.column('Modified', width=180, minwidth=120) self.tree.pack(fill=tk.BOTH, expand=True) # ========== 右侧内容区域 ========== right_frame = tk.Frame(main_paned) main_paned.add(right_frame) # 创建垂直分割面板 right_paned = tk.PanedWindow(right_frame, orient=tk.VERTICAL, sashrelief=tk.RAISED, sashwidth=4) right_paned.pack(fill=tk.BOTH, expand=True) # 磁盘布局可视化 cluster_frame = tk.LabelFrame(right_paned, text="磁盘布局可视化") right_paned.add(cluster_frame, height=450) # 创建画布和滚动条 canvas_frame = tk.Frame(cluster_frame) canvas_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) h_scroll = ttk.Scrollbar(canvas_frame, orient=tk.HORIZONTAL) v_scroll = ttk.Scrollbar(canvas_frame, orient=tk.VERTICAL) self.canvas = tk.Canvas( canvas_frame, bg='white', xscrollcommand=h_scroll.set, yscrollcommand=v_scroll.set ) h_scroll.pack(side=tk.BOTTOM, fill=tk.X) v_scroll.pack(side=tk.RIGHT, fill=tk.Y) self.canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) h_scroll.config(command=self.canvas.xview) v_scroll.config(command=self.canvas.yview) # 文件内容/详细信息 content_frame = tk.LabelFrame(right_paned, text="文件内容/详细信息") right_paned.add(content_frame) # 创建标签页 notebook_frame = tk.Frame(content_frame) notebook_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) self.notebook = ttk.Notebook(notebook_frame) self.notebook.pack(fill=tk.BOTH, expand=True) # 文件内容标签页 self.content_tab = ttk.Frame(self.notebook) self.notebook.add(self.content_tab, text="文件内容") text_frame = tk.Frame(self.content_tab) text_frame.pack(fill=tk.BOTH, expand=True) text_scroll = ttk.Scrollbar(text_frame) text_scroll.pack(side=tk.RIGHT, fill=tk.Y) self.text = tk.Text( text_frame, wrap=tk.WORD, yscrollcommand=text_scroll.set, font=('Consolas', 10) ) self.text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) text_scroll.config(command=self.text.yview) # 磁盘信息标签页 self.info_tab = ttk.Frame(self.notebook) self.notebook.add(self.info_tab, text="磁盘信息") info_frame = tk.Frame(self.info_tab) info_frame.pack(fill=tk.BOTH, expand=True) info_scroll = ttk.Scrollbar(info_frame) info_scroll.pack(side=tk.RIGHT, fill=tk.Y) self.info_text = tk.Text( info_frame, wrap=tk.WORD, yscrollcommand=info_scroll.set, state='disabled', font=('Consolas', 10) ) self.info_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) info_scroll.config(command=self.info_text.yview) # 属性信息标签页 self.attr_tab = ttk.Frame(self.notebook) self.notebook.add(self.attr_tab, text="属性信息") attr_frame = tk.Frame(self.attr_tab) attr_frame.pack(fill=tk.BOTH, expand=True) attr_scroll = ttk.Scrollbar(attr_frame) attr_scroll.pack(side=tk.RIGHT, fill=tk.Y) self.attr_text = tk.Text( attr_frame, wrap=tk.WORD, yscrollcommand=attr_scroll.set, font=('Consolas', 10) ) self.attr_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) attr_scroll.config(command=self.attr_text.yview) # 创建状态栏 status_frame = tk.Frame(self.root, height=25) status_frame.pack(side=tk.BOTTOM, fill=tk.X) self.status_var = tk.StringVar() self.status_var.set("就绪") status_bar = tk.Label( status_frame, textvariable=self.status_var, bd=1, relief=tk.SUNKEN, anchor=tk.W, padx=10 ) status_bar.pack(fill=tk.X) # 初始化数据 self.refresh_data() # 绑定事件 self.tree.bind("<<TreeviewSelect>>", self._on_tree_select) self.selected_clusters = [] # 添加右键菜单 self.context_menu = tk.Menu(self.root, tearoff=0) self.context_menu.add_command(label="查看文件内容", command=self.view_file_content) self.context_menu.add_command(label="查看簇链", command=self.view_cluster_chain) self.context_menu.add_command(label="查看属性", command=self.view_attributes) self.tree.bind("<Button-3>", self.show_context_menu) def refresh_data(self): """刷新数据""" try: self.status_var.set("正在读取FAT表...") self.root.update_idletasks() # 读取FAT表 self.fat = self.reader.read_fat_table() self.total_clusters = len(self.fat) // 4 # 获取簇状态 self.cluster_status = self.analyzer.get_cluster_status() # 填充目录树 self._populate_tree() # 绘制簇图 self._draw_clusters() # 更新磁盘信息 self._update_disk_info() self.status_var.set("就绪") except Exception as e: self.status_var.set(f"错误: {str(e)}") messagebox.showerror("初始化错误", f"无法读取FAT表: {str(e)}") traceback.print_exc() def refresh_view(self): """刷新视图""" try: self.status_var.set("刷新中...") self.root.update_idletasks() # 重新读取FAT表 self.fat = self.reader.read_fat_table() self.total_clusters = len(self.fat) // 4 self.cluster_status = self.analyzer.get_cluster_status() # 重新填充目录树 self._populate_tree() # 重新绘制簇图 self._draw_clusters() # 更新磁盘信息 self._update_disk_info() # 清空文件内容显示 self.text.delete('1.0', tk.END) self.attr_text.delete('1.0', tk.END) self.status_var.set("刷新完成") except Exception as e: self.status_var.set(f"刷新失败: {str(e)}") messagebox.showerror("刷新错误", str(e)) traceback.print_exc() def _update_disk_info(self): """更新磁盘信息标签页内容""" info_text = f"""FAT32 文件系统信息 卷标: {self.reader.get_volume_label()} 每扇区字节数: {self.reader.bytes_per_sector} 每簇扇区数: {self.reader.sectors_per_cluster} 簇大小: {self.reader.sectors_per_cluster * self.reader.bytes_per_sector} 字节 保留扇区数: {self.reader.reserved_sector_count} FAT表数量: {self.reader.num_fats} 每FAT表扇区数: {self.reader.sectors_per_fat} 根目录起始簇号: {self.reader.root_cluster} 总扇区数: {self.reader.total_sectors_32} 总簇数: {self.analyzer.total_clusters} FAT表偏移: 0x{self.reader.fat_offset:X} 数据区偏移: 0x{self.reader.data_offset:X} 空闲簇数: {self.cluster_status.count(0)} 已分配簇数: {self.cluster_status.count(1)} 保留簇数: {self.cluster_status.count(3)} """ self.info_text.config(state='normal') self.info_text.delete('1.0', tk.END) self.info_text.insert(tk.END, info_text) self.info_text.config(state='disabled') def _populate_tree(self): """填充目录树""" self.tree.delete(*self.tree.get_children()) self._add_dir(self.reader.root_cluster, '', "根目录", visited=set()) def _add_dir(self, cluster, parent, name, visited=None): """递归添加目录和文件到树中""" if visited is None: visited = set() if cluster in visited: return visited.add(cluster) # 添加目录节点 node = self.tree.insert( parent, 'end', text="📁", open=True, values=("目录", name, "", cluster, "", "") ) # 读取目录内容 try: # 根据用户设置决定是否包含特殊目录 include_special = self.show_special_var.get() entries = self.analyzer.read_directory(cluster, include_special=include_special) for entry in entries: # 应用过滤设置 if not self.show_hidden_var.get() and entry.get('is_hidden', False): continue if not self.show_system_var.get() and entry.get('is_system', False): continue if entry['is_dir']: # 添加子目录 self._add_dir(entry['start_cluster'], node, entry['name'], visited) else: # 添加文件 size_str = self._format_size(entry['size']) self.tree.insert( node, 'end', text="📄", values=( "文件", entry['name'], size_str, entry['start_cluster'], entry['created'], entry['modified'] ) ) except Exception as e: print(f"读取目录错误: {str(e)}") self.tree.insert( node, 'end', text="⚠️", values=("错误", f"读取错误: {str(e)}", "", "", "", "") ) def _format_size(self, size): """格式化文件大小 - 确保单位正确""" if size < 1024: return f"{size} B" elif size < 1024 * 1024: return f"{size/1024:.2f} KB" else: return f"{size/(1024*1024):.2f} MB" def _draw_clusters(self, highlight_clusters=None): """绘制簇状态可视化 - 优化布局""" self.canvas.delete('all') cols = 80 size = 8 pad = 1 max_show = min(len(self.cluster_status), 8000) # 最多显示8000个簇 # 添加标题 self.canvas.create_text( 10, 10, anchor='nw', text=f"簇状态可视化 (显示前 {max_show} 个簇)", font=('Arial', 10, 'bold'), fill="#333" ) # 计算最大行数 max_rows = (max_show + cols - 1) // cols # 绘制簇 for i in range(max_show): row = i // cols col = i % cols x0 = col * (size + pad) + 10 y0 = row * (size + pad) + 40 status = self.cluster_status[i] if status == 0: color = '#4CAF50' # 空闲 - 绿色 elif status == 1: color = '#F44336' # 已分配 - 红色 elif status == 2: color = '#FF9800' # 结束簇 - 橙色 else: color = '#9E9E9E' # 保留簇 - 灰色 if highlight_clusters and i in highlight_clusters: color = '#2196F3' # 选中文件簇链 - 蓝色 self.canvas.create_rectangle(x0, y0, x0+size, y0+size, fill=color, outline=color) # 将图例放在簇图下方 legend_y = max_rows * (size + pad) + 60 # 添加图例标题 self.canvas.create_text( 10, legend_y - 20, anchor='nw', text="图例:", font=('Arial', 9, 'bold'), fill="#333" ) # 添加图例项 legend_items = [ ('空闲簇', '#4CAF50'), ('已分配', '#F44336'), ('结束簇', '#FF9800'), ('保留簇', '#9E9E9E'), ('选中文件', '#2196F3') ] for idx, (text, color) in enumerate(legend_items): x_pos = 10 + idx * 120 self.canvas.create_rectangle( x_pos, legend_y, x_pos + 15, legend_y + 15, fill=color, outline='#333' ) self.canvas.create_text( x_pos + 20, legend_y + 7, anchor='w', text=text, font=('Arial', 9), fill="#333" ) # 添加统计信息 stats_text = f"总簇数: {len(self.cluster_status)} | " \ f"空闲簇: {self.cluster_status.count(0)} | " \ f"已分配簇: {self.cluster_status.count(1)} | " \ f"保留簇: {self.cluster_status.count(3)}" self.canvas.create_text( 10, legend_y + 30, anchor='nw', text=stats_text, font=('Arial', 9), fill="#333" ) # 设置画布滚动区域 self.canvas.config(scrollregion=(0, 0, 700, legend_y + 60)) def _on_tree_select(self, event): """处理目录树选择事件""" item = self.tree.selection() if not item: return node = item[0] values = self.tree.item(node, 'values') # 如果没有值,跳过 if not values: return # 如果是文件节点 if values[0] == "文件": file_name = values[1] size_str = values[2] start_cluster = int(values[3]) size = self._parse_size(size_str) # 获取文件簇链 clusters = self.analyzer.get_file_clusters(start_cluster) self.selected_clusters = clusters # 高亮显示文件簇链 highlight_clusters = [c for c in clusters if c < 8000] # 只高亮前8000个簇 self._draw_clusters(highlight_clusters=highlight_clusters) # 显示文件信息 self.text.config(state='normal') self.text.delete('1.0', tk.END) self.text.insert(tk.END, f"文件信息: {file_name}\n") self.text.insert(tk.END, "-" * 80 + "\n") self.text.insert(tk.END, f"大小: {size_str}\n") self.text.insert(tk.END, f"起始簇: {start_cluster}\n") self.text.insert(tk.END, f"簇链: {clusters}\n") self.text.insert(tk.END, f"创建时间: {values[4]}\n") self.text.insert(tk.END, f"修改时间: {values[5]}\n") self.text.insert(tk.END, "-" * 80 + "\n\n") # 读取文件内容 try: data = self._read_file_content(clusters, size) # 尝试解码为文本 try: text_content = data.decode('utf-8', errors='replace') self.text.insert(tk.END, "文件内容:\n") self.text.insert(tk.END, "-" * 80 + "\n") self.text.insert(tk.END, text_content) except UnicodeDecodeError: # 显示十六进制预览 self.text.insert(tk.END, "十六进制预览 (仅显示前1KB):\n") self.text.insert(tk.END, "-" * 80 + "\n") hex_view = "" for i in range(0, min(len(data), 1024), 16): chunk = data[i:i+16] hexstr = ' '.join(f'{b:02X}' for b in chunk) asciistr = ''.join(chr(b) if 32 <= b < 127 else '.' for b in chunk) hex_view += f"{i:04X}: {hexstr.ljust(47)} {asciistr}\n" self.text.insert(tk.END, hex_view) except Exception as e: self.text.insert(tk.END, f"读取文件内容失败: {str(e)}") # 切换到文件内容标签页 self.notebook.select(self.content_tab) else: self.selected_clusters = [] self._draw_clusters() self.text.config(state='normal') self.text.delete('1.0', tk.END) self.text.insert(tk.END, f"目录信息: {values[1]}\n") self.text.insert(tk.END, f"起始簇: {values[3] if values else '未知'}\n") self.text.config(state='disabled') def _parse_size(self, size_str): """解析文件大小字符串为字节数""" if 'KB' in size_str: return int(float(size_str.replace(' KB', '')) * 1024) elif 'MB' in size_str: return int(float(size_str.replace(' MB', '')) * 1024 * 1024) elif 'B' in size_str: return int(size_str.replace(' B', '')) return 0 def _read_file_content(self, clusters, size): """读取文件内容""" data = b'' for c in clusters: data += self.reader.read_cluster(c) if len(data) >= size: break return data[:size] def show_context_menu(self, event): """显示右键上下文菜单""" item = self.tree.identify_row(event.y) if item: self.tree.selection_set(item) self.context_menu.post(event.x_root, event.y_root) def view_file_content(self): """查看文件内容(已通过树选择实现)""" pass def view_cluster_chain(self): """查看簇链(已通过树选择实现)""" pass def view_attributes(self): """查看文件属性""" item = self.tree.selection() if not item: return node = item[0] values = self.tree.item(node, 'values') if not values or values[0] != "文件": return self.attr_text.delete('1.0', tk.END) self.attr_text.insert(tk.END, f"文件属性: {values[1]}\n\n") # 显示文件在树中的详细信息 self.attr_text.insert(tk.END, "基本信息:\n") self.attr_text.insert(tk.END, f"文件名: {values[1]}\n") self.attr_text.insert(tk.END, f"大小: {values[2]}\n") self.attr_text.insert(tk.END, f"起始簇: {values[3]}\n") self.attr_text.insert(tk.END, f"创建时间: {values[4]}\n") self.attr_text.insert(tk.END, f"修改时间: {values[5]}\n\n") # 获取文件簇链 clusters = self.analyzer.get_file_clusters(int(values[3])) self.attr_text.insert(tk.END, f"簇链 ({len(clusters)} 簇):\n") self.attr_text.insert(tk.END, f"{clusters}\n") # 切换到属性标签页 self.notebook.select(self.attr_tab) def create_file_dialog(self): """创建新文件对话框""" filename = simpledialog.askstring("新建文件", "输入文件名(支持长文件名):") if not filename: return # 检查文件名是否包含非法字符 invalid_chars = ['\\', '/', ':', '*', '?', '"', '<', '>', '|'] if any(char in filename for char in invalid_chars): messagebox.showerror("错误", f"文件名包含非法字符: {''.join(invalid_chars)}") return content = simpledialog.askstring("新建文件", "输入文件内容:") if content is None: return try: self.status_var.set(f"正在写入文件 {filename}...") self.root.update_idletasks() # 写入文件 clusters = self.reader.write_file_to_root(filename, content.encode('utf-8')) self.status_var.set(f"文件 {filename} 写入成功,刷新中...") self.root.update_idletasks() # 刷新视图 self.refresh_data() messagebox.showinfo("成功", f"文件 {filename} 已成功写入根目录!") self.status_var.set("就绪") # 高亮显示新文件的簇链 highlight_clusters = [c for c in clusters if c < 8000] self._draw_clusters(highlight_clusters=highlight_clusters) except Exception as e: self.status_var.set(f"写入失败: {str(e)}") messagebox.showerror("写入失败", str(e)) traceback.print_exc() def run(self): """启动GUI主循环""" self.root.mainloop() if __name__ == "__main__": # 获取设备路径 device_path = input("请输入U盘设备路径(例如: /dev/sdb1 或 \\\\.\\PhysicalDrive1): ") try: # 创建FAT32读写器 writer = FAT32Writer(device_path) # 创建并运行GUI gui = FAT32GUI(writer) gui.run() # 关闭资源 writer.close() except Exception as e: print(f"发生错误: {str(e)}") traceback.print_exc() input("按回车键退出..."输出界面数据填充有误
最新发布
07-06
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值