fix: 还原 cmd /c type 读取并移除路径字符黑名单 — 加密钻带文件只能用 cmd type 读取

b75439c 以"命令行是多余的"为由把读取改成 FileStream+StreamReader,是错误前提:
项目存在无法用标准文件 I/O 读取的加密 txt/钻带文件(CLAUDE.md "Encrypted file reading"、
Docs/readme.md "读取加密钻带(使用cmd命令type读取)"、
Docs/刀具列表拖动排序功能说明.md "加密文件:使用 cmd 命令读取"均有记录),
该改动会导致这些文件读取失败。本次回退。

改动:
- 还原 CommandTypeFileReader.cs:cmd.exe /d /c type(ANSI/936 解码),
  失败时退出码与 stderr 保留在异常消息里
- 删除 ForbiddenPathChars 与两处字符校验(本次报错根因):
  ( ) & ^ @ 空格在双引号内对 type 安全,黑名单只会误伤 (D2-f)、(1) 等正常文件名
- 保留"非空路径""文件存在"校验与 ReadAllText(path, encoding) 重载
- 删除 AnsiFileReader.cs,15 处调用点改回
- CLAUDE.md / .trellis/spec/:恢复"加密文件必须用 cmd type"的说明,
  并标注为不可优化掉的硬约束(含 b75439c 反例)

验证:demo_drl 全部 36 个文件与 2335894 实现逐字符一致、行数一致;
含 ( ) & 空格路径实测可读(旧黑名单实现抛"文件路径包含不允许的命令行特殊字符");
cmd 失败消息含退出码与 stderr;dotnet build -p:Platform=x86 0 错误,
警告集与 HEAD 基线完全一致。
This commit is contained in:
2026-09-20 13:28:37 +08:00
parent b75439cff4
commit 209268ee18
11 changed files with 168 additions and 143 deletions

View File

@@ -1,59 +0,0 @@
using System;
using System.IO;
using System.Text;
namespace DrillTools
{
/// <summary>
/// 钻带/参数文件统一读取入口。
/// 默认按 ANSI/GB2312(code page 936) 解码,等价于历史 `cmd type` 实现的字节透传语义,
/// 但不经过命令行,因此路径中的特殊字符(如 `(` `)` `&` `%` `!` `^` `@` 空格)不会影响读取。
/// </summary>
internal static class AnsiFileReader
{
private const int Gb2312CodePage = 936;
public static string ReadAllText(string filePath)
{
return ReadAllText(filePath, CreateAnsiEncoding());
}
public static string ReadAllText(string filePath, Encoding encoding)
{
string normalizedPath = ValidateAndNormalizePath(filePath);
try
{
using var stream = new FileStream(normalizedPath, FileMode.Open, FileAccess.Read, FileShare.Read);
// detectEncodingFromByteOrderMarks: true 与历史实现保持一致:
// 原先经 Process.StandardOutput(StreamReader) 解码,默认会探测 BOM,
// 因此带 UTF-8/UTF-16 BOM 的文件解码结果保持不变。
using var reader = new StreamReader(stream, encoding, detectEncodingFromByteOrderMarks: true);
return reader.ReadToEnd();
}
catch (Exception ex) when (ex is IOException || ex is UnauthorizedAccessException)
{
throw new InvalidOperationException($"读取文件失败:{normalizedPath}({ex.Message})", ex);
}
}
private static string ValidateAndNormalizePath(string filePath)
{
if (string.IsNullOrWhiteSpace(filePath))
throw new ArgumentException("文件路径不能为空", nameof(filePath));
string normalizedPath = Path.GetFullPath(filePath);
if (!File.Exists(normalizedPath))
throw new FileNotFoundException($"文件不存在:{normalizedPath}", normalizedPath);
return normalizedPath;
}
private static Encoding CreateAnsiEncoding()
{
return Encoding.GetEncoding(Gb2312CodePage);
}
}
}

View File

@@ -39,7 +39,7 @@ namespace DrillTools
ShutdownMode = ShutdownMode.OnExplicitShutdown; ShutdownMode = ShutdownMode.OnExplicitShutdown;
// 只读取和解析文件一次,提取所有需要的信息 // 只读取和解析文件一次,提取所有需要的信息
string content = AnsiFileReader.ReadAllText(filePath); string content = CommandTypeFileReader.ReadAllText(filePath);
var viewModel = new MainWindowViewModel var viewModel = new MainWindowViewModel
{ {
IsStartupDrillTapeFile = true, IsStartupDrillTapeFile = true,
@@ -150,7 +150,7 @@ namespace DrillTools
ShouldCheckSortFileOnLoad = false ShouldCheckSortFileOnLoad = false
}; };
string content = AnsiFileReader.ReadAllText(filePath); string content = CommandTypeFileReader.ReadAllText(filePath);
viewModel.OriginalFilePath = filePath; viewModel.OriginalFilePath = filePath;
viewModel.LoadToolsFromDrillTape(content); viewModel.LoadToolsFromDrillTape(content);
@@ -185,7 +185,7 @@ namespace DrillTools
ShouldCheckSortFileOnLoad = false ShouldCheckSortFileOnLoad = false
}; };
string content = AnsiFileReader.ReadAllText(filePath); string content = CommandTypeFileReader.ReadAllText(filePath);
viewModel.LoadToolsFromDrillTape(content); viewModel.LoadToolsFromDrillTape(content);
string outputFilePath = viewModel.GeneratePpDrillTape(); string outputFilePath = viewModel.GeneratePpDrillTape();
@@ -232,7 +232,7 @@ namespace DrillTools
var result = scalingWindow.ScalingResult.Value; var result = scalingWindow.ScalingResult.Value;
string content = AnsiFileReader.ReadAllText(filePath); string content = CommandTypeFileReader.ReadAllText(filePath);
string scaledContent = Integration.ExcellonScaler.Scale(content, result.Kx, result.Ky); string scaledContent = Integration.ExcellonScaler.Scale(content, result.Kx, result.Ky);
// 在源文件所在目录下新建子文件夹 dr2 x{InputX} y{InputY} // 在源文件所在目录下新建子文件夹 dr2 x{InputX} y{InputY}

View File

@@ -60,7 +60,7 @@ Supported file extensions: `.txt`, `.drl`, `.dr2`, `.dpin`
## Important Technical Details ## Important Technical Details
- **File encoding**: ANSI/GB2312 (code page 936) — drill tape files use this encoding, not UTF-8 - **File encoding**: ANSI/GB2312 (code page 936) — drill tape files use this encoding, not UTF-8
- **File reading**: Drill tape / parameter files are read through `AnsiFileReader` (direct `FileStream` + `StreamReader` with ANSI/936 as the default encoding, BOM detection kept as before), never through a command line. Paths containing `(`, `)`, `&`, `%`, `!`, `^`, `@` or spaces read fine. The old `cmd.exe /c type` reader was removed on 2026-09-20 — there is no encryption layer (verified: all `demo_drl/` samples decode identically via plain file I/O). - **Encrypted file reading**: Uses `cmd.exe /c type` to read drill tape files (some files are "encrypted" and cannot be read directly via standard file I/O). Drill tape / parameter files are read through `CommandTypeFileReader` — **do not "optimize" this into standard file I/O (`File.ReadAllText` / `FileStream` / `StreamReader`)**: that was tried in `b75439c` (2026-09-20) and broke reading of encrypted files, and was reverted. Paths are no longer filtered by a character blacklist: `(`, `)`, `&`, `^`, `@` and spaces are safe inside the quoted `type` argument, and the old blacklist only produced false positives on normal file names such as `(D2-f)` and `(1)`.
- **Slot hole calculation**: Uses CAM350 standard tolerance (0.0127mm) for computing hole counts along slot paths - **Slot hole calculation**: Uses CAM350 standard tolerance (0.0127mm) for computing hole counts along slot paths
- **Backup strategy**: Creates `.bak` files when saving modified drill tape data - **Backup strategy**: Creates `.bak` files when saving modified drill tape data
- **Command-line support**: App accepts a file path argument to auto-load a drill tape file on startup - **Command-line support**: App accepts a file path argument to auto-load a drill tape file on startup

96
CommandTypeFileReader.cs Normal file
View File

@@ -0,0 +1,96 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Text;
namespace DrillTools
{
/// <summary>
/// 钻带/参数文件统一读取入口。
/// <para>
/// <b>为什么必须走 <c>cmd.exe /d /c type</c></b>:项目存在部分“加密”的 txt/钻带文件,
/// 无法用标准文件 I/O(<c>File.ReadAllText</c> / <c>FileStream</c> / <c>StreamReader</c>)直接读取,
/// 只能通过 <c>type</c> 命令读出明文。历史上曾有提交(<c>b75439c</c>,2026-09-20)
/// 以“命令行是多余的”为由改成直接文件 I/O,导致这些加密文件读取失败,已被回退。
/// 参见 <c>CLAUDE.md</c> 的 “Encrypted file reading” 与
/// <c>.trellis/spec/backend/database-guidelines.md</c>。
/// </para>
/// <para>
/// <b>为什么不做路径字符黑名单</b>:路径被双引号包裹后,<c>( ) &amp; ^ @</c> 与空格对 <c>type</c> 都是安全的
/// (双引号内 cmd 不把 <c>&amp;</c>/<c>^</c> 当分隔符),而黑名单会误伤
/// <c>(D2-f)</c>、<c>(1)</c> 这类正常业务文件名。因此这里只校验“非空”与“文件存在”。
/// </para>
/// <para>
/// <b>已知残留限制(cmd 语义,不拦截)</b>:路径中出现 <c>%VAR%</c> 形状片段时会发生变量展开
/// (双引号内同样展开)导致读取失败;路径中的 <c>!</c> 仅在延迟展开开启(<c>/v:on</c> 或注册表
/// <c>DelayedExpansion=1</c>)时才会失败。这类片段无法通过字符黑名单区分,故不做校验。
/// </para>
/// </summary>
internal static class CommandTypeFileReader
{
private const int Gb2312CodePage = 936;
public static string ReadAllText(string filePath)
{
return ReadAllText(filePath, CreateAnsiEncoding());
}
public static string ReadAllText(string filePath, Encoding encoding)
{
string normalizedPath = ValidateAndNormalizePath(filePath);
using var process = new Process
{
StartInfo = CreateStartInfo(normalizedPath, encoding)
};
if (!process.Start())
throw new InvalidOperationException("无法启动文件读取进程");
var outputTask = process.StandardOutput.ReadToEndAsync();
var errorTask = process.StandardError.ReadToEndAsync();
process.WaitForExit();
string output = outputTask.GetAwaiter().GetResult();
string error = errorTask.GetAwaiter().GetResult();
if (process.ExitCode != 0)
throw new InvalidOperationException($"读取文件失败,退出代码:{process.ExitCode},错误:{error.Trim()}");
return output;
}
private static ProcessStartInfo CreateStartInfo(string filePath, Encoding encoding)
{
return new ProcessStartInfo
{
FileName = "cmd.exe",
Arguments = $"/d /c type \"{filePath}\"",
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true,
StandardOutputEncoding = encoding,
StandardErrorEncoding = encoding
};
}
private static string ValidateAndNormalizePath(string filePath)
{
if (string.IsNullOrWhiteSpace(filePath))
throw new ArgumentException("文件路径不能为空", nameof(filePath));
string normalizedPath = Path.GetFullPath(filePath);
if (!File.Exists(normalizedPath))
throw new FileNotFoundException($"文件不存在:{normalizedPath}", normalizedPath);
return normalizedPath;
}
private static Encoding CreateAnsiEncoding()
{
return Encoding.GetEncoding(Gb2312CodePage);
}
}
}

View File

@@ -1,7 +1,8 @@
# 含特殊字符路径的钻带文件读取修复说明 # 含特殊字符路径的钻带文件读取修复说明
> 修复日期:2026-09-20(任务 `.trellis/tasks/09-20-fix-special-char-path-read`) > 修复日期:2026-09-20(任务 `.trellis/tasks/09-20-restore-cmd-type-reader`)
> 影响面:所有钻带/参数文件读取入口(共 15 处调用) > 影响面:所有钻带/参数文件读取入口(共 15 处调用)
> 本文件替换了同日先前一版的错误结论(“不存在加密层 / 可不经命令行”),见下文“上一轮的错误结论”。
## 问题描述 ## 问题描述
@@ -16,92 +17,79 @@
- `1009B-2013钻机参数表(D2-f)(A1A2厂) (1).doc` - `1009B-2013钻机参数表(D2-f)(A1A2厂) (1).doc`
- `demo_drl (1)/xx.drl` - `demo_drl (1)/xx.drl`
## 问题分析 ## 根因
### 直接原因 读取统一入口 `CommandTypeFileReader.ReadAllText` 通过 `cmd.exe /d /c type "<path>"` 读取文件:
读取统一入口 `CommandTypeFileReader.ReadAllText` 把文件路径拼进命令行,用子进程读文件:
```csharp ```csharp
FileName = "cmd.exe", FileName = "cmd.exe",
Arguments = $"/d /c type \"{filePath}\"", Arguments = $"/d /c type \"{filePath}\"",
``` ```
因为路径会成为命令行的组成部分,代码在 `ValidateAndNormalizePath` 里加了一串字符黑名单 因为路径会成为命令行的一部分,`03d9b8b`(2026-05-16)在 `ValidateAndNormalizePath` 中引入了一串
(`ForbiddenPathChars`)“防炸”,命中即抛异常。 字符黑名单(`ForbiddenPathChars` = `& | < > ^ % ! ( ) @ " \r \n`),命中即抛异常。
### 历史成因(git 证据) **这层字符黑名单是过度防御**:真正会被 cmd 语义破坏的只有 `%VAR%` 形状片段(变量展开在双引号内同样发生)
和延迟展开开启时的 `!`,而 `(` `)` `&` `^` `@` 空格在双引号内完全安全。黑名单把安全字符一并禁掉,
直接误伤 `(D2-f)`、`(1)` 这类正常业务文件名。
| 提交 | 内容 | ## 为什么必须保留 `cmd.exe /d /c type`(而**不能**改成标准文件 I/O)
|---|---|
| `44de1cb`(2025-12-17) | “优化 sort.txt 读取方式…改用 cmd type 命令读取…提升编码兼容性”。真实动因是编码:.NET 6 下 `Encoding.Default` = UTF-8,读 GB2312 文件乱码,而当时 `Encoding.GetEncoding(936)` 因未注册 CodePages provider 会抛异常,于是绕道 `cmd type` + `StandardOutputEncoding = 936` |
| `473b2ca`(2026-05-23) | 注册 `CodePagesEncodingProvider`,删掉 `GetEncoding` 的 catch 回退 —— 真正的根因已修,但没拆掉多余的 cmd 层 |
| `03d9b8b`(2026-05-16,Refactor) | 把散落的内联 `ProcessStartInfo` 抽成 `CommandTypeFileReader`,**首次**引入 `ForbiddenPathChars` 黑名单 |
即:把路径拼进命令行 ⇒ 必须“防炸” ⇒ 造出黑名单。黑名单并非业务需求,是实现的自我约束。 项目存在部分**加密的 txt/钻带文件,无法用标准文件 I/O(`File.ReadAllText` / `FileStream` / `StreamReader`)
直接读取**,只能靠 `type` 命令读出明文。这条项目事实原本就记录在 `CLAUDE.md`:
### 实测结论(Windows 10.0.26100,`cmd /d /c type "<path>"`) > **Encrypted file reading**: Uses `cmd.exe /c type` to read drill tape files
> (some files are "encrypted" and cannot be read directly via standard file I/O)
### 上一轮的错误结论(已被本次回退)
`b75439c`(2026-09-20)以“`type` 只是字节透传、命令行是多余的”为前提,把读取改成
`FileStream` + `StreamReader` 直读并删除 `CommandTypeFileReader`。该前提是**错的**:
- 该提交的动机来自历史问题(.NET 6 下 `Encoding.Default` = UTF-8 导致 GB2312 乱码,
当年 `Encoding.GetEncoding(936)` 因未注册 CodePages provider 会抛异常)。编码问题确实早已由
`473b2ca`(注册 `CodePagesEncodingProvider`)解决,但**“编码问题已解决”不等于“cmd type 可以删”**——
保留 cmd type 的真实理由是加密文件。
- 上轮检索时关键词只用了 `cmd type`,而 `CLAUDE.md` 的记录写作 `cmd.exe /c type`,因此漏检,
随后复核又把这条项目事实当作“没有证据的说法”删除。
因此本次恢复 `CommandTypeFileReader`,并在 `CLAUDE.md` 与 `.trellis/spec/` 中把
“必须经 cmd type 读取加密文件、禁止改成标准文件 I/O”写成硬约束,避免再次被“优化掉”。
## 本次改动
1. 恢复 `CommandTypeFileReader.cs`(`git show 2335894:CommandTypeFileReader.cs` 为基线),
读取仍为 `cmd.exe /d /c type "<path>"`,`StandardOutputEncoding`/`StandardErrorEncoding` 仍为 ANSI/936;
失败时保留退出码与 stderr:`InvalidOperationException("读取文件失败,退出代码:{code},错误:{stderr}")`。
2. 删除 `ForbiddenPathChars` 与 `ValidateAndNormalizePath` 中两处字符校验(原始路径一次、规范化路径一次)。
路径只保留“非空”`ArgumentException("文件路径不能为空")` 与“文件存在”`FileNotFoundException("文件不存在:...")`。
3. 删除 `b75439c` 引入的 `AnsiFileReader.cs`;15 处调用点由 `AnsiFileReader.ReadAllText` 改回
`CommandTypeFileReader.ReadAllText`。
4. 保留 `ReadAllText(filePath, encoding)` 重载;默认编码仍为 ANSI/GB2312(code page 936)。
5. 修正 `CLAUDE.md`、`.trellis/spec/backend/`、`.trellis/spec/guides/index.md` 中上一轮被改错的描述。
## 实测结论(Windows 10.0.26100,`cmd /d /c type "<path>"`)
| 路径中的字符 | 实测结果 | 说明 | | 路径中的字符 | 实测结果 | 说明 |
|---|---|---| |---|---|---|
| `(` `)` `&` `^` `@` 空格 | **可正常读取** | 双引号内 cmd 不把 `&`/`^` 当分隔符 | | `(` `)` `&` `^` `@` 空格 | **可正常读取** | 双引号内 cmd 不把 `&`/`^` 当分隔符;本次黑名单移除的直接依据 |
| `%CD%` / `%TEMP%` / `%USERNAME%` | **读取失败**(rc=1) | 变量展开在双引号内**依然发生** | | `%CD%` / `%TEMP%` / `%USERNAME%` | **读取失败**(rc=1) | 变量展开在双引号内**依然发生** —— 已知残留限制 |
| `!CD!` | 默认可读;`/v:on` 时失败 | 依赖延迟展开开关 | | `!CD!` | 默认可读;`/v:on` 时失败 | 依赖延迟展开开关 —— 已知残留限制 |
| `<` `>` `|` `"` 与 CR/LF | 在 Windows 文件名中本就非法 | 无需额外拦截 |
| 含 `0x1A` 等二进制字节 | 字节完全一致 | `type` 是字节透传,不做任何转换 | | 含 `0x1A` 等二进制字节 | 字节完全一致 | `type` 是字节透传,不做任何转换 |
结论:黑名单属于**过度防御** —— 真正会坏的只有 `%VAR%`(必坏)和 `!VAR!`(延迟展开开启时坏), ## 已知残留限制(cmd 语义,本次不解决)
却把 `(` `)` `&` `^` `@` 等完全安全的字符一并禁掉,直接误伤 `(D2-f)`、`(1)` 这类正常文件名。
而 `type` 既然只是字节透传,这层 cmd 对编码问题其实毫无贡献。
## 解决方案 - 路径中出现 `%VAR%` 形状片段时会变量展开,导致读取失败。
- 路径中的 `!` 仅在延迟展开开启(`cmd /v:on` 或注册表 `DelayedExpansion=1`)时失败。
1. 新增 `AnsiFileReader.cs`(原 `CommandTypeFileReader.cs` 已删除),改为直接文件 IO: 这两类片段无法通过字符黑名单与正常文件名区分,故不拦截;如需彻底解决需另立任务
(例如改用短路径/临时副本等 cmd 之外的方案),本次范围不含。
```csharp
using var stream = new FileStream(normalizedPath, FileMode.Open, FileAccess.Read, FileShare.Read);
using var reader = new StreamReader(stream, encoding, detectEncodingFromByteOrderMarks: true);
return reader.ReadToEnd();
```
`detectEncodingFromByteOrderMarks: true` 是为了**保持旧行为**:原先经 `Process.StandardOutput`(内部是
`StreamReader`)解码时默认会探测 BOM,所以带 UTF-8/UTF-16 BOM 的文件仍然按 BOM 指定的编码解码。
2. 删除 `ForbiddenPathChars` 与两处字符校验;路径只保留“非空”与“文件存在”两项校验。
3. IO 异常统一包装为可诊断消息:`InvalidOperationException("读取文件失败:{路径}({原因})")`,并保留 `innerException`。
4. 默认编码仍为 ANSI/GB2312(code page 936),`ReadAllText(filePath, encoding)` 重载能力不变。
5. 同步更新 `.trellis/spec/backend/` 中 6 处已失效的规范描述(`cmd type`、危险字符校验、进程退出码)。
## 验证结果
用真实 `demo_drl/` 样例与旧实现对照(同一编码解码):
| 验证项 | 结果 |
|---|---|
| 14 个 demo 样例:新旧实现字符串**逐字符一致、行数一致** | ✅ PASS |
| 特殊字符路径可读:`demo (a&b) 100%/xx (1) -A&B.drl`、`yy (2)!^.drl` | ✅ PASS |
| `%VAR%` 字面路径可读:`pct%TEMP%end/pct%TEMP%end (3).drl`(旧实现 rc=1 失败) | ✅ PASS |
| BOM 行为一致:UTF-8 BOM / UTF-16 LE / UTF-16 BE 文件新旧实现结果相同 | ✅ PASS |
| 文件被独占占用 → `InvalidOperationException("读取文件失败:...")` 且 `InnerException is IOException` | ✅ PASS |
| 空路径 → `ArgumentException("文件路径不能为空")` | ✅ PASS |
| 不存在路径 → `FileNotFoundException("文件不存在:...")` | ✅ PASS |
| `dotnet build -p:Platform=x86` | 0 错误,无新增警告 |
## 行为一致性说明(解码层)
本次只替换“怎么拿到字节”(去掉命令行),**没有改变“怎么解码”**:
| 场景 | 旧实现(cmd type + StreamReader) | 新实现(FileStream + StreamReader) |
|---|---|---|
| 无 BOM 的 GB2312/ANSI 文件 | 936 解码 | 936 解码(一致) |
| UTF-8 BOM 文件 | 探测到 BOM → 改用 UTF-8、去掉 BOM | 相同(一致) |
| UTF-16 LE/BE BOM 文件 | 探测到 BOM → 按 UTF-16 解码 | 相同(一致) |
| 含 `0x1A` 等二进制字节 | 字节透传后 936 解码 | 相同(一致) |
即:保留历史实现**隐式具备**的 BOM 支持,而不是新增特性,也不是回归。
若将来要改成固定 936(不探测 BOM),属于行为变更,需要单独任务与用户确认。
## 后续约定 ## 后续约定
- 钻带/参数文件读取统一走 `AnsiFileReader.ReadAllText`,**不要再引入子进程/命令行方式读文件**。 - 钻带/参数文件读取统一走 `CommandTypeFileReader.ReadAllText`:**必须保留 `cmd.exe /d /c type`**,
- 不要对文件路径做字符黑名单校验:读取不经过命令行,此类限制只会误伤正常文件名。 不要为了“去掉命令行”改成 `File.ReadAllText` / `File.ReadAllBytes` / `FileStream` / `StreamReader`
(加密文件会读取失败,`b75439c` 已试过并被回退)。
- 不要再对文件路径做字符黑名单校验:双引号内 `( ) & ^ @` 与空格对 `type` 安全,黑名单只会误伤正常文件名。
- 需要更换编码时使用 `ReadAllText(filePath, encoding)` 重载,不要另起第二套读取逻辑。 - 需要更换编码时使用 `ReadAllText(filePath, encoding)` 重载,不要另起第二套读取逻辑。

View File

@@ -70,7 +70,7 @@ namespace DrillTools
public static void ClearParametersAndSave(string filePath) public static void ClearParametersAndSave(string filePath)
{ {
string content = AnsiFileReader.ReadAllText(filePath); string content = CommandTypeFileReader.ReadAllText(filePath);
string cleanedContent = ClearParameters(content); string cleanedContent = ClearParameters(content);
string backupFilePath = filePath + ".bak"; string backupFilePath = filePath + ".bak";

View File

@@ -57,7 +57,7 @@ namespace DrillTools
public static void ReplaceParameters(string drlFilePath, string paramFilePath, out List<double> unmatchedDiameters) public static void ReplaceParameters(string drlFilePath, string paramFilePath, out List<double> unmatchedDiameters)
{ {
// 1. 读取参数文件,提取第一行描述和直径→参数字符串映射 // 1. 读取参数文件,提取第一行描述和直径→参数字符串映射
string paramContent = AnsiFileReader.ReadAllText(paramFilePath); string paramContent = CommandTypeFileReader.ReadAllText(paramFilePath);
var paramLines = DrillTapeLineParser.SplitLines(paramContent); var paramLines = DrillTapeLineParser.SplitLines(paramContent);
string paramDescription = ExtractDescription(paramLines); string paramDescription = ExtractDescription(paramLines);
@@ -70,7 +70,7 @@ namespace DrillTools
throw new InvalidOperationException("参数文件中未找到有效的参数行(格式:T00C<直径>H...)"); throw new InvalidOperationException("参数文件中未找到有效的参数行(格式:T00C<直径>H...)");
// 2. 读取 .drl 文件 // 2. 读取 .drl 文件
string drlContent = AnsiFileReader.ReadAllText(drlFilePath); string drlContent = CommandTypeFileReader.ReadAllText(drlFilePath);
// 3. 执行替换(同时收集未匹配直径) // 3. 执行替换(同时收集未匹配直径)
var unmatchedSet = new HashSet<double>(); var unmatchedSet = new HashSet<double>();

View File

@@ -62,7 +62,7 @@ namespace DrillTools
{ {
try try
{ {
string content = AnsiFileReader.ReadAllText(filePath); string content = CommandTypeFileReader.ReadAllText(filePath);
var lines = DrillTapeLineParser.SplitLines(content); var lines = DrillTapeLineParser.SplitLines(content);
if (lines.Count > 0) if (lines.Count > 0)
{ {

View File

@@ -65,7 +65,7 @@ namespace DrillTools
// 异步加载文件以避免阻塞UI // 异步加载文件以避免阻塞UI
await System.Threading.Tasks.Task.Run(() => await System.Threading.Tasks.Task.Run(() =>
{ {
string drillTapeContent = AnsiFileReader.ReadAllText(filePath); string drillTapeContent = CommandTypeFileReader.ReadAllText(filePath);
// 在UI线程中更新界面 // 在UI线程中更新界面
this.Dispatcher.Invoke(() => this.Dispatcher.Invoke(() =>
@@ -142,7 +142,7 @@ namespace DrillTools
ViewModel.IsStartupDrillTapeFile = false; ViewModel.IsStartupDrillTapeFile = false;
ViewModel.OriginalFilePath = openFileDialog.FileName; ViewModel.OriginalFilePath = openFileDialog.FileName;
string drillTapeContent = AnsiFileReader.ReadAllText(openFileDialog.FileName); string drillTapeContent = CommandTypeFileReader.ReadAllText(openFileDialog.FileName);
ViewModel.LoadToolsFromDrillTape(drillTapeContent); ViewModel.LoadToolsFromDrillTape(drillTapeContent);
} }
@@ -336,7 +336,7 @@ namespace DrillTools
ViewModel.IsStartupDrillTapeFile = false; ViewModel.IsStartupDrillTapeFile = false;
ViewModel.OriginalFilePath = files[0]; ViewModel.OriginalFilePath = files[0];
string drillTapeContent = AnsiFileReader.ReadAllText(files[0]); string drillTapeContent = CommandTypeFileReader.ReadAllText(files[0]);
ViewModel.LoadToolsFromDrillTape(drillTapeContent); ViewModel.LoadToolsFromDrillTape(drillTapeContent);
} }

View File

@@ -1670,7 +1670,7 @@ M30";
try try
{ {
string fileContent = AnsiFileReader.ReadAllText(sortFilePath); string fileContent = CommandTypeFileReader.ReadAllText(sortFilePath);
// 按行分割内容 // 按行分割内容
string[] lines = fileContent.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); string[] lines = fileContent.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
@@ -2255,7 +2255,7 @@ M30";
throw new System.IO.FileNotFoundException($"参考钻带文件不存在:{referenceFilePath}"); throw new System.IO.FileNotFoundException($"参考钻带文件不存在:{referenceFilePath}");
} }
string drillTapeContent = AnsiFileReader.ReadAllText(referenceFilePath); string drillTapeContent = CommandTypeFileReader.ReadAllText(referenceFilePath);
if (string.IsNullOrWhiteSpace(drillTapeContent)) if (string.IsNullOrWhiteSpace(drillTapeContent))
{ {

View File

@@ -45,7 +45,7 @@ namespace DrillTools
throw new InvalidOperationException("参数文件中未找到有效的参数行(格式:T00C<直径>S...)"); throw new InvalidOperationException("参数文件中未找到有效的参数行(格式:T00C<直径>S...)");
// 2. 读取 .rou 文件 // 2. 读取 .rou 文件
string rouContent = AnsiFileReader.ReadAllText(rouFilePath); string rouContent = CommandTypeFileReader.ReadAllText(rouFilePath);
// 3. 执行替换(同时收集未匹配直径) // 3. 执行替换(同时收集未匹配直径)
var unmatchedSet = new HashSet<double>(); var unmatchedSet = new HashSet<double>();
@@ -70,7 +70,7 @@ namespace DrillTools
{ {
var dict = new Dictionary<double, string>(); var dict = new Dictionary<double, string>();
string content = AnsiFileReader.ReadAllText(paramFilePath); string content = CommandTypeFileReader.ReadAllText(paramFilePath);
var lines = DrillTapeLineParser.SplitLines(content); var lines = DrillTapeLineParser.SplitLines(content);
foreach (var line in lines) foreach (var line in lines)