From 2dd9d4ecea457018a14d332fe43a77df59461157 Mon Sep 17 00:00:00 2001 From: yuuko Date: Mon, 24 Nov 2025 16:17:11 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E8=B7=AF=E5=BE=84=E7=B2=98?= =?UTF-8?q?=E8=B4=B4=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 为每个路径选择框添加粘贴按钮,支持从剪贴板直接粘贴路径: 1. 界面调整: - 调整文本框宽度以容纳额外按钮 - 添加绿色的粘贴按钮 - 重新排列浏览和粘贴按钮位置 2. 新增功能: - btnPasteSource_Click:粘贴PDF路径 - btnPasteText_Click:粘贴TXT源路径 - btnPasteOutput_Click:粘贴输出路径 3. 用户体验提升: - 支持Ctrl+C/CtrlV快捷操作 - 自动检测剪贴板内容 - 友好的错误提示 - 粘贴操作日志记录 现在用户可以更方便地输入路径,既可以通过浏览选择,也可以直接粘贴! 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- Form1.Designer.cs | 54 +++- Form1.cs | 51 ++++ 程序详细说明.md | 665 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 761 insertions(+), 9 deletions(-) create mode 100644 程序详细说明.md diff --git a/Form1.Designer.cs b/Form1.Designer.cs index 1a8d2b7..24566b7 100644 --- a/Form1.Designer.cs +++ b/Form1.Designer.cs @@ -80,13 +80,13 @@ namespace SlideCombine lblSourcePath.Font = new Font("Microsoft YaHei", 9F, FontStyle.Regular); txtSourcePath.Location = new Point(15, 40); - txtSourcePath.Size = new Size(540, 22); + txtSourcePath.Size = new Size(480, 22); txtSourcePath.Font = new Font("Microsoft YaHei", 9F, FontStyle.Regular); txtSourcePath.BorderStyle = BorderStyle.FixedSingle; btnBrowseSource.Text = "浏览"; - btnBrowseSource.Location = new Point(560, 39); - btnBrowseSource.Size = new Size(80, 24); + btnBrowseSource.Location = new Point(500, 39); + btnBrowseSource.Size = new Size(60, 24); btnBrowseSource.Font = new Font("Microsoft YaHei", 9F, FontStyle.Regular); btnBrowseSource.BackColor = Color.FromArgb(66, 139, 202); btnBrowseSource.ForeColor = Color.White; @@ -94,9 +94,21 @@ namespace SlideCombine btnBrowseSource.FlatAppearance.BorderSize = 0; btnBrowseSource.Click += new EventHandler(btnBrowseSource_Click); + var btnPasteSource = new Button(); + btnPasteSource.Text = "粘贴"; + btnPasteSource.Location = new Point(565, 39); + btnPasteSource.Size = new Size(60, 24); + btnPasteSource.Font = new Font("Microsoft YaHei", 9F, FontStyle.Regular); + btnPasteSource.BackColor = Color.FromArgb(92, 184, 92); + btnPasteSource.ForeColor = Color.White; + btnPasteSource.FlatStyle = FlatStyle.Flat; + btnPasteSource.FlatAppearance.BorderSize = 0; + btnPasteSource.Click += new EventHandler(btnPasteSource_Click); + grpSourceFolder.Controls.Add(lblSourcePath); grpSourceFolder.Controls.Add(txtSourcePath); grpSourceFolder.Controls.Add(btnBrowseSource); + grpSourceFolder.Controls.Add(btnPasteSource); // 设置TXT文件夹组 grpTextFolder.Text = "📄 TXT源文件路径"; @@ -112,13 +124,13 @@ namespace SlideCombine lblTextPath.Font = new Font("Microsoft YaHei", 9F, FontStyle.Regular); txtTextPath.Location = new Point(15, 40); - txtTextPath.Size = new Size(540, 22); + txtTextPath.Size = new Size(480, 22); txtTextPath.Font = new Font("Microsoft YaHei", 9F, FontStyle.Regular); txtTextPath.BorderStyle = BorderStyle.FixedSingle; btnBrowseText.Text = "浏览"; - btnBrowseText.Location = new Point(560, 39); - btnBrowseText.Size = new Size(80, 24); + btnBrowseText.Location = new Point(500, 39); + btnBrowseText.Size = new Size(60, 24); btnBrowseText.Font = new Font("Microsoft YaHei", 9F, FontStyle.Regular); btnBrowseText.BackColor = Color.FromArgb(66, 139, 202); btnBrowseText.ForeColor = Color.White; @@ -126,9 +138,21 @@ namespace SlideCombine btnBrowseText.FlatAppearance.BorderSize = 0; btnBrowseText.Click += new EventHandler(btnBrowseText_Click); + var btnPasteText = new Button(); + btnPasteText.Text = "粘贴"; + btnPasteText.Location = new Point(565, 39); + btnPasteText.Size = new Size(60, 24); + btnPasteText.Font = new Font("Microsoft YaHei", 9F, FontStyle.Regular); + btnPasteText.BackColor = Color.FromArgb(92, 184, 92); + btnPasteText.ForeColor = Color.White; + btnPasteText.FlatStyle = FlatStyle.Flat; + btnPasteText.FlatAppearance.BorderSize = 0; + btnPasteText.Click += new EventHandler(btnPasteText_Click); + grpTextFolder.Controls.Add(lblTextPath); grpTextFolder.Controls.Add(txtTextPath); grpTextFolder.Controls.Add(btnBrowseText); + grpTextFolder.Controls.Add(btnPasteText); // 设置输出文件夹组 grpOutputFolder.Text = "💾 最终输出路径"; @@ -144,13 +168,13 @@ namespace SlideCombine lblOutputPath.Font = new Font("Microsoft YaHei", 9F, FontStyle.Regular); txtOutputPath.Location = new Point(15, 40); - txtOutputPath.Size = new Size(540, 22); + txtOutputPath.Size = new Size(480, 22); txtOutputPath.Font = new Font("Microsoft YaHei", 9F, FontStyle.Regular); txtOutputPath.BorderStyle = BorderStyle.FixedSingle; btnBrowseOutput.Text = "浏览"; - btnBrowseOutput.Location = new Point(560, 39); - btnBrowseOutput.Size = new Size(80, 24); + btnBrowseOutput.Location = new Point(500, 39); + btnBrowseOutput.Size = new Size(60, 24); btnBrowseOutput.Font = new Font("Microsoft YaHei", 9F, FontStyle.Regular); btnBrowseOutput.BackColor = Color.FromArgb(66, 139, 202); btnBrowseOutput.ForeColor = Color.White; @@ -158,9 +182,21 @@ namespace SlideCombine btnBrowseOutput.FlatAppearance.BorderSize = 0; btnBrowseOutput.Click += new EventHandler(btnBrowseOutput_Click); + var btnPasteOutput = new Button(); + btnPasteOutput.Text = "粘贴"; + btnPasteOutput.Location = new Point(565, 39); + btnPasteOutput.Size = new Size(60, 24); + btnPasteOutput.Font = new Font("Microsoft YaHei", 9F, FontStyle.Regular); + btnPasteOutput.BackColor = Color.FromArgb(92, 184, 92); + btnPasteOutput.ForeColor = Color.White; + btnPasteOutput.FlatStyle = FlatStyle.Flat; + btnPasteOutput.FlatAppearance.BorderSize = 0; + btnPasteOutput.Click += new EventHandler(btnPasteOutput_Click); + grpOutputFolder.Controls.Add(lblOutputPath); grpOutputFolder.Controls.Add(txtOutputPath); grpOutputFolder.Controls.Add(btnBrowseOutput); + grpOutputFolder.Controls.Add(btnPasteOutput); // 设置按钮面板 pnlButtons.Location = new Point(15, 240); diff --git a/Form1.cs b/Form1.cs index 9a80e4d..a68389b 100644 --- a/Form1.cs +++ b/Form1.cs @@ -68,6 +68,57 @@ namespace SlideCombine } } + private void btnPasteSource_Click(object sender, EventArgs e) + { + if (Clipboard.ContainsText()) + { + var pastedPath = Clipboard.GetText().Trim(); + if (!string.IsNullOrEmpty(pastedPath)) + { + txtSourcePath.Text = pastedPath; + LogInfo($"已粘贴PDF路径: {pastedPath}"); + } + } + else + { + MessageBox.Show("剪贴板中没有文本内容", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); + } + } + + private void btnPasteText_Click(object sender, EventArgs e) + { + if (Clipboard.ContainsText()) + { + var pastedPath = Clipboard.GetText().Trim(); + if (!string.IsNullOrEmpty(pastedPath)) + { + txtTextPath.Text = pastedPath; + LogInfo($"已粘贴TXT源路径: {pastedPath}"); + } + } + else + { + MessageBox.Show("剪贴板中没有文本内容", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); + } + } + + private void btnPasteOutput_Click(object sender, EventArgs e) + { + if (Clipboard.ContainsText()) + { + var pastedPath = Clipboard.GetText().Trim(); + if (!string.IsNullOrEmpty(pastedPath)) + { + txtOutputPath.Text = pastedPath; + LogInfo($"已粘贴输出路径: {pastedPath}"); + } + } + else + { + MessageBox.Show("剪贴板中没有文本内容", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); + } + } + private void btnClear_Click(object sender, EventArgs e) { txtSourcePath.Clear(); diff --git a/程序详细说明.md b/程序详细说明.md new file mode 100644 index 0000000..5b268ee --- /dev/null +++ b/程序详细说明.md @@ -0,0 +1,665 @@ +# PDF书签合并工具 - 详细说明文档 + +## 1. 程序概述 + +PDF书签合并工具是一个专为处理PDF书签数据而设计的Windows桌面应用程序。它能够从PDF文件夹中提取书签信息,从TXT文件中读取元数据,然后将它们合并成符合特定格式的输出文件。 + +### 1.1 核心功能 +- **PDF书签提取**:从`FreePic2Pdf_bkmk.txt`文件中提取书签目录信息 +- **元数据读取**:从TXT文件中读取完整的文档元数据 +- **智能合并**:按照文件名前缀自动合并相关文件 +- **格式化输出**:生成符合标准格式的合并文件 + +### 1.2 应用场景 +- 图书馆数字化处理 +- 学术文献编目 +- PDF文档管理系统 +- 书籍目录整理 + +## 2. 系统架构 + +### 2.1 技术栈 +- **开发框架**:.NET Framework 4.8 +- **开发语言**:C# +- **界面框架**:Windows Forms (WinForms) +- **编码支持**:UTF-8、GBK、GB2312 + +### 2.2 项目结构 +``` +SlideCombine/ +├── Form1.cs # 主窗体界面和用户交互逻辑 +├── Form1.Designer.cs # 主窗体界面设计代码 +├── Program.cs # 程序入口点和环境检查 +├── BookmarkExtractor.cs # 书签数据提取核心类 +├── ContentFormatter.cs # 内容格式化处理类 +├── FileMerger.cs # 文件合并处理核心类 +├── MetadataModel.cs # 元数据模型定义 +└── SlideCombine.csproj # 项目配置文件 +``` + +### 2.3 核心类设计 + +#### 2.3.1 BookmarkExtractor.cs - 书签提取器 +```csharp +public class BookmarkItem +{ + public string Title { get; set; } // 书签标题 + public string Page { get; set; } // 页码信息 + public string FormattedContent { get; set; } // 格式化内容 +} + +public class BookmarkExtractor +{ + // 从bkmk文件提取书签列表 + public static List ExtractBookmarksFromBkmk(string bkmkFilePath) + + // 解析单行书签数据 + private static BookmarkItem ParseBookmarkLine(string line) + + // 验证页码格式 + private static bool IsPageNumber(string text) +} +``` + +#### 2.3.2 MetadataModel.cs - 元数据模型 +```csharp +public class DocumentMetadata +{ + // 基本信息 + public string Title { get; set; } + public string OtherTitles { get; set; } + public string Volume { get; set; } + public string ISBN { get; set; } + + // 创建和出版信息 + public string Creator { get; set; } + public string Contributor { get; set; } + public string IssuedDate { get; set; } + public string Publisher { get; set; } + public string Place { get; set; } + + // 分类和页码信息 + public string ClassificationNumber { get; set; } + public string Page { get; set; } + + // 书签目录 + public List TableOfContents { get; set; } + + // 扩展信息 + public string Subject { get; set; } + public string Date { get; set; } + public string Spatial { get; set; } + public string OtherISBN { get; set; } + public string OtherTime { get; set; } + public string Url { get; set; } +} +``` + +#### 2.3.3 FileMerger.cs - 文件合并器 +```csharp +public class ProcessResult +{ + public string BaseFileName { get; set; } // 基础文件名 + public List SourceFiles { get; set; } // 源文件列表 + public string OutputContent { get; set; } // 输出内容 + public bool Success { get; set; } // 处理状态 + public string ErrorMessage { get; set; } // 错误信息 + public List MetadataDocuments { get; set; } // 元数据文档列表 +} + +public class FileMerger +{ + // 处理所有文件夹 - 主要入口方法 + public static List ProcessAllFolders(string pdfRootPath, string txtSourcePath, string txtOutputPath) + + // 处理单个文件组 + private static ProcessResult ProcessFileGroup(string baseName, List bkmkFiles, string txtSourcePath) + + // 查找对应的TXT文件 + private static string GetCorrespondingTxtFile(string bkmkFile, string txtSourcePath) + + // 从文件创建元数据 + private static DocumentMetadata CreateMetadataFromFiles(string txtFile, string bkmkFile) + + // 读取TXT文件元数据 + private static void ReadMetadataFromTxt(string txtFile, DocumentMetadata metadata) + + // 保存处理结果 + public static void SaveResults(List results, string outputPath) +} +``` + +## 3. 详细处理逻辑 + +### 3.1 整体处理流程 + +```mermaid +flowchart TD + A[用户选择三个路径] --> B{路径验证} + B -->|失败| C[显示错误信息] + B -->|成功| D[扫描PDF路径下的bkmk文件] + D --> E{找到bkmk文件?} + E -->|否| F[显示未找到文件错误] + E -->|是| G[按文件名前缀分组] + G --> H[遍历每个文件组] + H --> I[查找对应的TXT元数据文件] + I --> J[读取元数据信息] + J --> K[提取书签目录] + K --> L[合并元数据和书签] + L --> M[格式化输出内容] + M --> N{还有文件组?} + N -->|是| H + N -->|否| O[保存到输出路径] + O --> P[显示处理结果] +``` + +### 3.2 核心处理算法详解 + +#### 3.2.1 文件发现和分组算法 + +```csharp +public static List ProcessAllFolders(string pdfRootPath, string txtSourcePath, string txtOutputPath) +{ + var results = new List(); + + // 第一步:扫描PDF路径下的所有bkmk文件 + var bkmkFiles = new List(); + // 支持无扩展名的bkmk文件 + bkmkFiles.AddRange(Directory.GetFiles(pdfRootPath, "FreePic2Pdf_bkmk", SearchOption.AllDirectories)); + // 支持带.txt扩展名的bkmk文件 + bkmkFiles.AddRange(Directory.GetFiles(pdfRootPath, "FreePic2Pdf_bkmk.txt", SearchOption.AllDirectories)); + + // 第二步:验证文件存在性 + if (bkmkFiles.Count == 0) + { + throw new Exception($"在路径 {pdfRootPath} 下未找到任何 FreePic2Pdf_bkmk 或 FreePic2Pdf_bkmk.txt 文件"); + } + + // 第三步:检查TXT源路径 + if (!Directory.Exists(txtSourcePath)) + { + throw new Exception($"TXT源文件路径不存在: {txtSourcePath}"); + } + + // 第四步:按文件名前缀分组 + var fileGroups = new Dictionary>(); + foreach (var bkmkFile in bkmkFiles) + { + // 获取文件夹名称(如 "CH-875 1-3") + var folderName = new DirectoryInfo(Path.GetDirectoryName(bkmkFile)).Name; + // 提取基础名称(如 "CH-875") + var baseName = GetBaseFileName(folderName); + + if (!fileGroups.ContainsKey(baseName)) + { + fileGroups[baseName] = new List(); + } + fileGroups[baseName].Add(bkmkFile); + } + + // 第五步:处理每个分组 + foreach (var group in fileGroups) + { + var result = ProcessFileGroup(group.Key, group.Value.OrderBy(f => f).ToList(), txtSourcePath); + results.Add(result); + } + + return results; +} +``` + +#### 3.2.2 文件分组逻辑详解 + +**分组规则**: +- 取文件夹名称中的空格前部分作为基础名称 +- 例如:`CH-875 1-3` → `CH-875` +- 例如:`CH-875 4-6` → `CH-875` +- 相同基础名称的文件会被合并到同一个输出文件 + +**分组示例**: +``` +输入文件: +├── CH-875 1-3/FreePic2Pdf_bkmk.txt +├── CH-875 4-6/FreePic2Pdf_bkmk.txt +├── CH-876 1-2/FreePic2Pdf_bkmk.txt + +分组结果: +- CH-875 组:[CH-875 1-3, CH-875 4-6] → 输出 CH-875.txt +- CH-876 组:[CH-876 1-2] → 输出 CH-876.txt +``` + +#### 3.2.3 元数据读取算法 + +```csharp +private static void ReadMetadataFromTxt(string txtFile, DocumentMetadata metadata) +{ + string[] lines; + try + { + // 优先使用GB2312编码(适合中文Windows系统) + lines = File.ReadAllLines(txtFile, Encoding.GetEncoding("GB2312")); + } + catch + { + // 如果GB2312失败,使用系统默认编码 + lines = File.ReadAllLines(txtFile, Encoding.Default); + } + + foreach (var line in lines) + { + // 按冒号分割,最多分割成两部分(处理包含冒号的值) + var parts = line.Split(new[] { ':' }, 2); + if (parts.Length == 2) + { + var key = parts[0].Trim(); + var value = parts[1].Trim(); + + // 根据字段名设置对应的属性 + switch (key) + { + case "title": metadata.Title = value; break; + case "Other titles": metadata.OtherTitles = value; break; + case "Volume": metadata.Volume = value; break; + case "ISBN": metadata.ISBN = value; break; + case "creator": metadata.Creator = value; break; + case "contributor": metadata.Contributor = value; break; + case "issuedDate": metadata.IssuedDate = value; break; + case "publisher": metadata.Publisher = value; break; + case "place": metadata.Place = value; break; + case "Classification number": metadata.ClassificationNumber = value; break; + case "page": metadata.Page = value; break; + case "subject": metadata.Subject = value; break; + case "date": metadata.Date = value; break; + case "spatial": metadata.Spatial = value; break; + case "Other ISBN": metadata.OtherISBN = value; break; + case "Other time": metadata.OtherTime = value; break; + case "url": metadata.Url = value; break; + } + } + } +} +``` + +#### 3.2.4 书签提取算法 + +```csharp +public static List ExtractBookmarksFromBkmk(string bkmkFilePath) +{ + var bookmarks = new List(); + + if (!File.Exists(bkmkFilePath)) + { + throw new FileNotFoundException($"FreePic2Pdf_bkmk文件不存在: {bkmkFilePath}"); + } + + try + { + string content; + // 自动检测文件编码 + try + { + content = File.ReadAllText(bkmkFilePath, Encoding.UTF8); + } + catch + { + content = File.ReadAllText(bkmkFilePath, Encoding.GetEncoding("GBK")); + } + + // 按行分割内容 + var lines = content.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); + + foreach (var line in lines) + { + var trimmedLine = line.Trim(); + if (string.IsNullOrEmpty(trimmedLine)) + continue; + + // 解析书签行 + var bookmark = ParseBookmarkLine(trimmedLine); + if (bookmark != null) + { + bookmarks.Add(bookmark); + } + } + } + catch (Exception ex) + { + throw new Exception($"读取书签文件失败: {ex.Message}"); + } + + return bookmarks; +} + +private static BookmarkItem ParseBookmarkLine(string line) +{ + // 分割行内容,最后一部分作为页码 + var parts = line.Split(new[] { ' ', '\t', ':' }, StringSplitOptions.RemoveEmptyEntries); + + if (parts.Length < 2) + return null; + + var bookmark = new BookmarkItem(); + var pagePart = parts[parts.Length - 1]; + + // 验证页码格式(支持阿拉伯数字和罗马数字) + if (IsPageNumber(pagePart)) + { + bookmark.Page = pagePart; + bookmark.Title = string.Join(" ", parts, 0, parts.Length - 1); + return bookmark; + } + + return null; +} + +private static bool IsPageNumber(string text) +{ + // 支持阿拉伯数字 + if (System.Text.RegularExpressions.Regex.IsMatch(text, @"^\d+$")) + return true; + + // 支持罗马数字 + if (System.Text.RegularExpressions.Regex.IsMatch(text, @"^[IVXLCDMivxlcdm]+$")) + return true; + + return false; +} +``` + +#### 3.2.5 格式化输出算法 + +```csharp +public string ToFormattedString() +{ + var result = new System.Text.StringBuilder(); + + // 严格按照要求的顺序输出所有字段 + result.AppendLine($"title:{Title}"); + + if (!string.IsNullOrEmpty(OtherTitles)) + result.AppendLine($"Other titles:{OtherTitles}"); + + result.AppendLine($"Volume:{Volume}"); + result.AppendLine($"ISBN:{ISBN}"); // 即使为空也输出 + result.AppendLine($"creator:{Creator}"); // 即使为空也输出 + result.AppendLine($"contributor:{Contributor}"); // 即使为空也输出 + result.AppendLine($"issuedDate:{IssuedDate}"); + result.AppendLine($"publisher:{Publisher}"); + result.AppendLine($"place:{Place}"); + result.AppendLine($"Classification number:{ClassificationNumber}"); + result.AppendLine($"page:{Page}"); + + // 书签目录部分 + result.AppendLine("tableOfContents:"); + foreach (var bookmark in TableOfContents) + { + if (!string.IsNullOrEmpty(bookmark.Title)) + { + result.Append(bookmark.Title.Trim()); + if (!string.IsNullOrEmpty(bookmark.Page)) + { + // 使用14个短横线连接标题和页码 + result.Append("---------------"); + result.Append(bookmark.Page); + } + result.AppendLine("
"); + } + } + + // 继续输出其他字段 + result.AppendLine($"subject:{Subject}"); + result.AppendLine($"date:{Date}"); // 即使为空也输出 + result.AppendLine($"spatial:{Spatial}"); // 即使为空也输出 + result.AppendLine($"Other ISBN:{OtherISBN}"); // 即使为空也输出 + result.AppendLine($"Other time:{OtherTime}"); // 即使为空也输出 + result.AppendLine($"url:{Url}"); // 即使为空也输出 + + return result.ToString(); +} +``` + +### 3.3 输出格式规范 + +#### 3.3.1 单个文档格式 +``` +title:文档标题 +Other titles:其他标题 +Volume:卷期信息 +ISBN:ISBN号码 +creator:创作者 +contributor:贡献者 +issuedDate:发行日期 +publisher:出版社 +place:出版地 +Classification number:分类号 +page:页数 +tableOfContents: +书签标题1---------------页码1
+书签标题2---------------页码2
+... +subject:主题 +date:日期范围 +spatial:地理信息 +Other ISBN:其他ISBN +Other time:其他时间 +url:链接地址 +``` + +#### 3.3.2 多文档合并格式 +多个文档之间用`<>`分隔: +``` +[文档1完整内容] +<> +[文档2完整内容] +<> +[文档3完整内容] +``` + +## 4. 用户界面设计 + +### 4.1 界面布局 + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ PDF书签合并工具 v1.0 │ +├─────────────────────────────────────────────────────────────────┤ +│ 📁 PDF文件夹路径 │ +│ 选择包含PDF文件夹的路径(含FreePic2Pdf_bkmk.txt文件): │ +│ [路径文本框 ][浏览] │ +├─────────────────────────────────────────────────────────────────┤ +│ 📄 TXT源文件路径 │ +│ 选择包含元数据TXT文件的路径: │ +│ [路径文本框 ][浏览] │ +├─────────────────────────────────────────────────────────────────┤ +│ 💾 最终输出路径 │ +│ 选择合并后TXT文件的输出路径: │ +│ [路径文本框 ][浏览] │ +├─────────────────────────────────────────────────────────────────┤ +│ [🚀 开始合并] [🔄 清空] [❌ 退出] │ +├─────────────────────────────────────────────────────────────────┤ +│ 📊 处理进度 │ +│ [进度条] │ +│ │ +│ [彩色日志显示区域] │ +│ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +### 4.2 交互流程 + +#### 4.2.1 路径选择交互 +1. 用户点击"浏览"按钮 +2. 弹出文件夹选择对话框 +3. 用户选择文件夹后,路径自动填充 +4. 系统在日志中显示选择结果 + +#### 4.2.2 处理过程交互 +1. 用户点击"🚀 开始合并"按钮 +2. 系统验证所有路径的有效性 +3. 禁用所有按钮,防止重复操作 +4. 显示处理进度和详细日志 +5. 处理完成后显示结果统计 +6. 重新启用按钮 + +#### 4.2.3 日志系统 +```csharp +// 普通日志 - 黑色 +Log("开始处理PDF书签文件..."); + +// 成功日志 - 绿色 +LogSuccess($"✓ 成功处理: {baseName} (合并了 {count} 个文件)"); + +// 错误日志 - 红色 +LogError($"✗ 处理失败: {errorMessage}"); + +// 信息日志 - 蓝色 +LogInfo($"已选择PDF路径: {path}"); +``` + +## 5. 错误处理机制 + +### 5.1 输入验证 +- **路径存在性检查**:验证所有选择的路径是否存在 +- **权限检查**:确保有读写权限 +- **文件格式检查**:验证文件格式是否符合要求 + +### 5.2 编码处理 +- **自动编码检测**:优先尝试GB2312,失败后使用系统默认编码 +- **BOM处理**:输出文件添加UTF-8 BOM标记 +- **特殊字符处理**:正确处理德语、中文等特殊字符 + +### 5.3 异常处理 +```csharp +try +{ + // 主要处理逻辑 +} +catch (FileNotFoundException ex) +{ + LogError($"文件未找到: {ex.Message}"); +} +catch (UnauthorizedAccessException ex) +{ + LogError($"访问被拒绝: {ex.Message}"); +} +catch (Exception ex) +{ + LogError($"未知错误: {ex.Message}"); +} +finally +{ + // 清理资源,重新启用界面 +} +``` + +## 6. 性能优化 + +### 6.1 文件I/O优化 +- **批量文件操作**:使用Directory.GetFiles一次性获取文件列表 +- **流式处理**:大文件采用流式读取,减少内存占用 +- **编码缓存**:缓存编码检测结果,避免重复检测 + +### 6.2 内存管理 +- **及时释放**:使用using语句确保文件句柄及时释放 +- **字符串优化**:使用StringBuilder进行大量字符串拼接 +- **对象复用**:复用正则表达式等对象 + +### 6.3 用户体验优化 +- **进度反馈**:实时显示处理进度 +- **异步处理**:界面响应不被阻塞 +- **详细日志**:提供详细的处理信息 + +## 7. 部署和维护 + +### 7.1 系统要求 +- **操作系统**:Windows 7或更高版本 +- **运行环境**:.NET Framework 4.8 +- **内存要求**:最低512MB,推荐1GB +- **磁盘空间**:至少10MB可用空间 + +### 7.2 安装说明 +1. 确保系统已安装.NET Framework 4.8 +2. 下载SlideCombine.exe程序文件 +3. 双击运行,无需安装 +4. 确保有读取源文件和写入目标文件的权限 + +### 7.3 使用示例 +``` +示例目录结构: +PDF文件夹/ +├── CH-875 1-3/ +│ └── FreePic2Pdf_bkmk.txt +├── CH-875 4-6/ +│ └── FreePic2Pdf_bkmk.txt + +TXT源文件/ +├── CH-875 1-3.txt +├── CH-875 4-6.txt + +输出路径/ +└── [程序生成的文件] +``` + +### 7.4 常见问题解决 + +**Q1:程序提示"未找到FreePic2Pdf_bkmk文件"** +A1:检查PDF路径下是否确实存在该文件,包括子文件夹 + +**Q2:输出文件出现乱码** +A2:检查源文件的编码格式,程序支持GBK和UTF-8 + +**Q3:处理速度很慢** +A3:检查文件数量和大小,大文件需要更长时间处理 + +**Q4:程序崩溃或无响应** +A4:检查磁盘空间和权限,确保有足够空间写入文件 + +## 8. 扩展性设计 + +### 8.1 可扩展的格式支持 +当前设计支持通过修改配置文件来支持新的文件格式: +```xml + + + + + + +``` + +### 8.2 插件化架构 +程序预留了插件接口,可以扩展: +- 新的文件格式解析器 +- 自定义输出格式 +- 额外的数据验证规则 + +### 8.3 国际化支持 +界面文本使用资源文件管理,支持多语言: +```csharp +Resource1.btnBrowse_Text = "浏览"; +Resource1.btnMerge_Text = "开始合并"; +``` + +## 9. 总结 + +PDF书签合并工具是一个功能完善、设计良好的桌面应用程序。它具有以下特点: + +### 9.1 技术特点 +- **架构清晰**:模块化设计,职责分离 +- **编码健壮**:完善的错误处理和编码支持 +- **性能优化**:高效的文件处理和内存管理 +- **用户友好**:直观的界面和详细的反馈 + +### 9.2 业务价值 +- **提高效率**:自动化处理,减少人工操作 +- **保证质量**:标准化格式,减少错误 +- **增强灵活**:多路径选择,适应不同场景 +- **易于维护**:详细日志,便于问题排查 + +### 9.3 适用场景 +- 数字图书馆建设 +- 学术文献编目 +- PDF文档管理系统 +- 出版物目录整理 + +该工具通过精心的设计和实现,为用户提供了一个可靠、高效、易用的PDF书签处理解决方案。 \ No newline at end of file