esp_extractor 0.8.1

A Rust library for extracting and applying translations to Bethesda ESP/ESM/ESL files
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
//! ESP 字符串提取工具 CLI
#![allow(clippy::ptr_arg)]
#![allow(clippy::useless_asref)]

#[cfg(feature = "cli")]
use clap::Parser;
use std::path::PathBuf;
use esp_extractor::{Plugin, ExtractedString, SUPPORTED_EXTENSIONS, LoadedPlugin};
use esp_extractor::StringFile;
use esp_extractor::group::{Group, GroupChild};

#[cfg(debug_assertions)]
use esp_extractor::EspDebugger;

#[cfg(feature = "cli")]
#[derive(Parser)]
#[command(name = "esp_extractor")]
#[command(about = "从ESP/ESM/ESL文件中提取可翻译字符串,或解析Bethesda字符串文件")]
#[command(version)]
struct Cli {
    /// 输入文件路径(ESP/ESM/ESL或字符串文件)
    #[arg(short, long)]
    input: PathBuf,
    
    /// 输出JSON文件路径
    #[arg(short, long)]
    output: Option<PathBuf>,
    
    /// 包含本地化字符串(通过ID)
    #[arg(long)]
    include_localized: bool,
    
    /// 包含所有字符串(跳过验证)
    #[arg(long)]
    unfiltered: bool,
    
    /// 显示插件统计信息
    #[arg(long)]
    stats: bool,
    
    /// 静默模式(仅输出错误)
    #[arg(long)]
    quiet: bool,
    
    /// 应用翻译:从JSON文件应用翻译到ESP文件
    #[arg(long)]
    apply_file: Option<PathBuf>,
    
    /// 应用翻译:从JSON字符串应用指定的翻译对象
    #[arg(long)]
    apply_jsonstr: Option<String>,
    
    /// 应用翻译:从标准输入读取JSON翻译对象
    #[arg(long)]
    apply_partial_stdin: bool,
    
    /// 测试模式:解析文件后直接重建,用于验证解析和重建逻辑
    #[arg(long)]
    test_rebuild: bool,
    
    /// 对比两个ESP文件的结构差异
    #[arg(long)]
    compare_files: Option<PathBuf>,
    
    /// 字符串文件操作:解析字符串文件并输出JSON
    #[arg(long)]
    parse_strings: Option<PathBuf>,

    /// 将插件转换为 ESL (Light Plugin) 格式
    /// 重编号所有 FormID,从 0x800 开始,最多支持 2048 个记录
    #[arg(long)]
    eslify: bool,
}

#[cfg(feature = "cli")]
fn main() -> Result<(), Box<dyn std::error::Error>> {
    let cli = Cli::parse();
    
    validate_input(&cli.input)?;
    validate_partial_options(&cli)?;
    
    // 处理不同的操作模式
    if cli.test_rebuild {
        return handle_test_rebuild(&cli);
    }
    
    if let Some(compare_file) = &cli.compare_files {
        return handle_file_comparison(&cli, compare_file);
    }
    
    if let Some(string_file) = &cli.parse_strings {
        return handle_string_file_parsing(&cli, string_file);
    }
    
    if cli.apply_partial_stdin {
        return handle_translation_stdin(&cli);
    }
    
    if let Some(translation_file) = &cli.apply_file {
        return handle_translation_file(&cli, translation_file);
    }
    
    if let Some(translation_json) = &cli.apply_jsonstr {
        return handle_translation_jsonstr(&cli, translation_json);
    }

    if cli.eslify {
        return handle_eslify(&cli);
    }

    // 默认模式:根据文件类型自动选择处理方式
    let extension = cli.input.extension()
        .and_then(|ext| ext.to_str())
        .map(|ext| ext.to_lowercase());
    
    let string_extensions = ["strings", "ilstrings", "dlstrings"];
    if string_extensions.iter().any(|&ext| Some(ext) == extension.as_deref()) {
        // 字符串文件:解析并输出JSON
        handle_string_file_parsing(&cli, &cli.input)
    } else {
        // ESP文件:字符串提取
        handle_string_extraction(&cli)
    }
}

/// 验证输入文件
fn validate_input(input: &PathBuf) -> Result<(), Box<dyn std::error::Error>> {
    if !input.exists() {
        return Err(format!("输入文件不存在: {:?}", input).into());
    }
    
    let extension = input.extension()
        .and_then(|ext| ext.to_str())
        .map(|ext| ext.to_lowercase());
    
    // 支持ESP/ESM/ESL文件和字符串文件
    let string_extensions = ["strings", "ilstrings", "dlstrings"];
    let is_esp_file = SUPPORTED_EXTENSIONS.iter().any(|&ext| Some(ext) == extension.as_deref());
    let is_string_file = string_extensions.iter().any(|&ext| Some(ext) == extension.as_deref());
    
    if !is_esp_file && !is_string_file {
        return Err("输入文件必须是ESP、ESM、ESL或字符串文件(STRINGS、ILSTRINGS、DLSTRINGS)".into());
    }
    
    Ok(())
}

/// 验证翻译选项(确保只使用一种方式)
fn validate_partial_options(cli: &Cli) -> Result<(), Box<dyn std::error::Error>> {
    let translation_count = [
        cli.apply_jsonstr.is_some(),
        cli.apply_file.is_some(),
        cli.apply_partial_stdin,
    ].iter().filter(|&&x| x).count();
    
    if translation_count > 1 {
        return Err("只能使用一种翻译方式:--apply-jsonstr、--apply-file 或 --apply-partial-stdin".into());
    }
    
    Ok(())
}

/// 处理测试重建模式
fn handle_test_rebuild(cli: &Cli) -> Result<(), Box<dyn std::error::Error>> {
    if !cli.quiet {
        println!("测试模式:解析并重建文件 {:?}", cli.input);
    }

    let output_path = get_rebuild_output_path(cli);
    test_rebuild_file(cli.input.clone(), output_path.clone())?;

    if !cli.quiet {
        println!("测试完成,重建文件输出到: {:?}", output_path);
        println!("请使用文件对比工具检查原文件和重建文件是否一致");
    }

    Ok(())
}

/// 处理 ESL 转换(FormID 重编号)
fn handle_eslify(cli: &Cli) -> Result<(), Box<dyn std::error::Error>> {
    if !cli.quiet {
        println!("正在将插件转换为 ESL (Light Plugin) 格式...");
        println!("输入文件: {:?}", cli.input);
    }

    // 加载插件
    let mut plugin = Plugin::load(cli.input.clone())?;

    // 检查是否已经是轻量插件
    if plugin.is_light() {
        if !cli.quiet {
            println!("注意:该插件已经是 Light Plugin (ESL)");
        }
    }

    // 执行 FormID 重编号
    if !cli.quiet {
        println!("正在重编号 FormID...");
    }

    plugin.eslify_formids()?;

    // 确定输出路径
    let output_path = if let Some(ref output) = cli.output {
        output.clone()
    } else {
        // 默认输出路径:原文件名 + _esl 后缀
        let file_stem = cli.input.file_stem()
            .and_then(|s| s.to_str())
            .unwrap_or("output");
        let extension = cli.input.extension()
            .and_then(|s| s.to_str())
            .unwrap_or("esp");
        cli.input.parent()
            .unwrap_or(std::path::Path::new("."))
            .join(format!("{}_esl.{}", file_stem, extension))
    };

    // 保存修改后的插件
    if !cli.quiet {
        println!("正在保存到: {:?}", output_path);
    }

    plugin.write_to_file(output_path.clone())?;

    if !cli.quiet {
        println!("✓ ESL 转换完成!");
        println!("  输出文件: {:?}", output_path);
        println!("\n提示:");
        println!("  1. 请确保插件记录数不超过 2048 个");
        println!("  2. 使用 Creation Kit 或 xEdit 验证插件完整性");
        println!("  3. 如果需要,手动设置插件的 LightMaster 标志");
    }

    Ok(())
}

/// 处理翻译应用(从字符串)
fn handle_translation_jsonstr(cli: &Cli, translation_json: &str) -> Result<(), Box<dyn std::error::Error>> {
    #[cfg(debug_assertions)]
    if !cli.quiet {
        println!("正在应用翻译到: {:?} (从命令行参数)", cli.input);
    }
    
    let translations = parse_translation_json(translation_json)?;
    apply_translations(cli, translations)
}

/// 处理翻译应用(从文件)
fn handle_translation_file(cli: &Cli, translation_file: &PathBuf) -> Result<(), Box<dyn std::error::Error>> {
    if !translation_file.exists() {
        return Err(format!("翻译文件不存在: {:?}", translation_file).into());
    }
    
    #[cfg(debug_assertions)]
    if !cli.quiet {
        println!("正在应用翻译到: {:?} (从文件: {:?})", cli.input, translation_file);
    }
    
    let translation_json = std::fs::read_to_string(translation_file)
        .map_err(|e| format!("读取翻译文件失败: {}", e))?;
    
    let translations = parse_translation_json(&translation_json)?;
    apply_translations(cli, translations)
}

/// 处理翻译应用(从标准输入)
fn handle_translation_stdin(cli: &Cli) -> Result<(), Box<dyn std::error::Error>> {
    #[cfg(debug_assertions)]
    if !cli.quiet {
        println!("正在应用翻译到: {:?} (从标准输入)", cli.input);
    }
    
    if !cli.quiet {
        eprintln!("等待从标准输入读取JSON数据... (Ctrl+D结束输入)");
    }
    
    use std::io::Read;
    let mut buffer = String::new();
    std::io::stdin().read_to_string(&mut buffer)
        .map_err(|e| format!("从标准输入读取失败: {}", e))?;
    
    let translations = parse_translation_json(&buffer)?;
    apply_translations(cli, translations)
}

/// 解析翻译JSON
fn parse_translation_json(json_str: &str) -> Result<Vec<ExtractedString>, Box<dyn std::error::Error>> {
    serde_json::from_str(json_str)
        .map_err(|e| format!("解析翻译JSON失败: {}", e).into())
}

/// 应用翻译
fn apply_translations(cli: &Cli, translations: Vec<ExtractedString>) -> Result<(), Box<dyn std::error::Error>> {
    if translations.is_empty() {
        return Err("翻译数据为空".into());
    }
    
    if !cli.quiet {
        println!("准备应用 {} 个翻译条目", translations.len());
        
        // 显示前3个翻译条目的详细信息
        for (i, translation) in translations.iter().take(3).enumerate() {
            println!("翻译条目 {}: [{}] {} -> \"{}\"",
                i + 1,
                translation.form_id,
                translation.get_string_type(),
                if translation.text.chars().count() > 50 {
                    format!("{}...", translation.text.chars().take(50).collect::<String>())
                } else {
                    translation.text.clone()
                }
            );
        }
        if translations.len() > 3 {
            println!("... 还有 {} 个翻译条目", translations.len() - 3);
        }
    }
    
    let output_path = get_apply_output_path(cli);
    Plugin::apply_translations(cli.input.clone(), output_path.clone(), translations, None)
        .map_err(|e| format!("应用翻译失败: {}", e))?;
    
    if !cli.quiet {
        println!("翻译应用完成,输出到: {:?}", output_path);
    }
    
    Ok(())
}

/// 处理字符串提取
fn handle_string_extraction(cli: &Cli) -> Result<(), Box<dyn std::error::Error>> {
    #[cfg(debug_assertions)]
    if !cli.quiet {
        println!("正在解析插件: {:?}", cli.input);
    }

    if cli.stats {
        // --stats 仍使用旧路径以保持统计能力不变
        let loaded = LoadedPlugin::load_auto(cli.input.clone(), Some("english"))
            .map_err(|e| format!("解析插件失败: {}", e))?;
        println!("{}", loaded.plugin().get_stats());
        return Ok(());
    }

    let use_legacy = std::env::var("ESP_EXTRACTOR_USE_LEGACY_EXTRACT").ok().as_deref() == Some("1");

    let (strings, loaded_for_summary) = if use_legacy {
        let loaded = LoadedPlugin::load_auto(cli.input.clone(), Some("english"))
            .map_err(|e| format!("解析插件失败: {}", e))?;
        let strings = loaded.extract_strings();
        (strings, Some(loaded))
    } else {
        let strings = esp_extractor::fast_extract::extract_strings_fast(cli.input.as_ref(), "english")
            .map_err(|e| format!("快路径提取失败: {}", e))?;
        (strings, None)
    };

    let output_path = cli.output.as_ref()
        .map(|p| p.clone())
        .unwrap_or_else(|| cli.input.with_extension("json"));

    save_strings_to_file(&strings, &output_path)?;

    if !cli.quiet {
        print_extraction_summary(loaded_for_summary.as_ref().map(|l| l.plugin()), &strings, &output_path);
    }

    Ok(())
}

/// 将字符串保存到文件
fn save_strings_to_file(strings: &[ExtractedString], output_path: &PathBuf) -> Result<(), Box<dyn std::error::Error>> {
    let json_output = serde_json::to_string_pretty(strings)
        .map_err(|e| format!("序列化JSON失败: {}", e))?;
    
    std::fs::write(output_path, &json_output)
        .map_err(|e| format!("写入文件失败: {}", e).into())
}

/// 打印提取摘要信息
fn print_extraction_summary(plugin: Option<&Plugin>, strings: &[ExtractedString], output_path: &PathBuf) {
    #[cfg(debug_assertions)]
    let stats = plugin.map(|p| p.get_stats());
    
    #[cfg(debug_assertions)]
    {
        if let Some(stats) = stats {
            println!("扫描到 {} 个组(包含子组)", stats.group_count);
            println!("扫描到 {} 个记录", stats.record_count);
        }
    }
    
    println!("提取到 {} 个有效字符串", strings.len());
    println!("结果已写入: {:?}", output_path);
    
    // 显示样例字符串
    if !strings.is_empty() {
        println!("\n样例字符串:");
        for (i, string) in strings.iter().take(3).enumerate() {
            println!("{}. [{}] {}: \"{}\"",
                i + 1,
                string.form_id,
                string.get_string_type(),
                if string.text.chars().count() > 50 {
                    format!("{}...", string.text.chars().take(50).collect::<String>())
                } else {
                    string.text.clone()
                }
            );
        }
        
        if strings.len() > 3 {
            println!("... 还有 {} 个字符串", strings.len() - 3);
        }
    }
}

/// 获取重建输出路径
fn get_rebuild_output_path(cli: &Cli) -> PathBuf {
    cli.output.clone().unwrap_or_else(|| {
        let mut output = cli.input.clone();
        let stem = output.file_stem().unwrap().to_str().unwrap();
        let extension = output.extension().unwrap().to_str().unwrap();
        output.set_file_name(format!("{}_rebuilt.{}", stem, extension));
        output
    })
}

/// 获取应用翻译输出路径
fn get_apply_output_path(cli: &Cli) -> PathBuf {
    cli.output.clone().unwrap_or_else(|| {
        let mut output = cli.input.clone();
        let stem = output.file_stem().unwrap().to_str().unwrap();
        let extension = output.extension().unwrap().to_str().unwrap();
        output.set_file_name(format!("{}.{}", stem, extension));
        output
    })
}

/// 测试文件重建功能
fn test_rebuild_file(input_path: PathBuf, output_path: PathBuf) -> Result<(), Box<dyn std::error::Error>> {
    let plugin = Plugin::load(input_path.clone())?;
    
    #[cfg(debug_assertions)]
    {
        println!("解析完成:");
        println!("  插件名: {}", plugin.get_name());
        println!("  插件类型: {}", plugin.get_type());
        println!("  组数量: {}", plugin.groups.len());
    }
    
    // 生成调试信息(仅在debug模式下)
    generate_debug_info(&plugin, &input_path, &output_path)?;
    
    // 重建文件
    plugin.write_to_file(output_path.clone())?;
    
    // 文件大小对比
    compare_file_sizes(&input_path, &output_path)?;
    
    Ok(())
}

/// 生成调试信息
#[cfg(debug_assertions)]
fn generate_debug_info(plugin: &Plugin, input_path: &PathBuf, output_path: &PathBuf) -> Result<(), Box<dyn std::error::Error>> {
    let original_dump_path = input_path.with_extension("original.dump");
    println!("生成原始文件结构dump: {:?}", original_dump_path);
    EspDebugger::dump_file_structure(plugin, original_dump_path)?;
    
    // 解析重建文件并生成dump
    plugin.write_to_file(output_path.clone())?;
    let rebuilt_plugin = Plugin::load(output_path.clone())?;
    
    let rebuilt_dump_path = output_path.with_extension("rebuilt.dump");
    println!("生成重建文件结构dump: {:?}", rebuilt_dump_path);
    EspDebugger::dump_file_structure(&rebuilt_plugin, rebuilt_dump_path)?;
    
    let compare_path = input_path.with_extension("compare.txt");
    println!("生成结构对比报告: {:?}", compare_path);
    EspDebugger::compare_structures(input_path.clone(), output_path.clone(), compare_path)?;
    
    println!();
    println!("调试文件已生成:");
    println!("  - 原始文件结构: {:?}", input_path.with_extension("original.dump"));
    println!("  - 重建文件结构: {:?}", output_path.with_extension("rebuilt.dump"));
    println!("  - 结构对比报告: {:?}", input_path.with_extension("compare.txt"));
    println!();
    println!("请检查这些dump文件来诊断重建问题!");
    
    Ok(())
}

#[cfg(not(debug_assertions))]
fn generate_debug_info(_plugin: &Plugin, _input_path: &PathBuf, _output_path: &PathBuf) -> Result<(), Box<dyn std::error::Error>> {
    Ok(())
}

/// 比较文件大小
fn compare_file_sizes(_input_path: &PathBuf, _output_path: &PathBuf) -> Result<(), Box<dyn std::error::Error>> {
    #[cfg(debug_assertions)]
    let original_size = std::fs::metadata(_input_path)?.len();
    #[cfg(debug_assertions)]
    let rebuilt_size = std::fs::metadata(_output_path)?.len();
    
    #[cfg(debug_assertions)]
    {
        println!("文件大小对比:");
        println!("  原文件: {} 字节", original_size);
        println!("  重建文件: {} 字节", rebuilt_size);
        
        if original_size == rebuilt_size {
            println!("✓ 文件大小一致");
        } else {
            println!("⚠ 文件大小不一致,差异: {} 字节", (rebuilt_size as i64) - (original_size as i64));
        }
    }
    
    Ok(())
}

/// 处理文件对比
fn handle_file_comparison(cli: &Cli, compare_file: &PathBuf) -> Result<(), Box<dyn std::error::Error>> {
    if !compare_file.exists() {
        return Err(format!("对比文件不存在: {:?}", compare_file).into());
    }
    
    if !cli.quiet {
        println!("正在对比文件结构:");
        println!("  文件1: {:?}", cli.input);
        println!("  文件2: {:?}", compare_file);
    }
    
    let plugin1 = Plugin::load(cli.input.clone())?;
    let plugin2 = Plugin::load(compare_file.clone())?;
    
    // 对比基本信息
    println!("\n=== 基本信息对比 ===");
    println!("组数量: {} vs {}", plugin1.groups.len(), plugin2.groups.len());
    
    if plugin1.groups.len() != plugin2.groups.len() {
        println!("⚠️ 组数量不匹配!");
        return Ok(());
    }
    
    // 对比每个GRUP的大小
    println!("\n=== GRUP大小对比 ===");
    for (i, (group1, group2)) in plugin1.groups.iter().zip(plugin2.groups.iter()).enumerate() {
        let label1 = String::from_utf8_lossy(&group1.label);
        let label2 = String::from_utf8_lossy(&group2.label);
        
        if group1.size != group2.size {
            println!("⚠️ GRUP {} ('{}' vs '{}'): {} vs {} (差异: {})", 
                i, label1, label2, group1.size, group2.size, 
                (group2.size as i64) - (group1.size as i64));
                
            // 详细分析这个组的差异
            analyze_group_difference(group1, group2, i)?;
        } else {
            println!("✓ GRUP {} ('{}'): {} 字节 - 匹配", i, label1, group1.size);
        }
    }
    
    Ok(())
}

/// 分析组差异的详细原因
fn analyze_group_difference(group1: &Group, group2: &Group, group_index: usize) -> Result<(), Box<dyn std::error::Error>> {
    println!("  详细分析GRUP {}:", group_index);
    println!("    子元素数量: {} vs {}", group1.children.len(), group2.children.len());
    
    if group1.children.len() != group2.children.len() {
        println!("    ⚠️ 子元素数量不匹配!");
        return Ok(());
    }
    
    let mut total_diff = 0i64;
    
    for (i, (child1, child2)) in group1.children.iter().zip(group2.children.iter()).enumerate() {
        match (child1, child2) {
            (GroupChild::Record(r1), GroupChild::Record(r2)) => {
                if r1.data_size != r2.data_size {
                    let diff = (r2.data_size as i64) - (r1.data_size as i64);
                    total_diff += diff;
                    println!("    记录 {} ({}): {} vs {} (差异: {})", 
                        i, r1.record_type, r1.data_size, r2.data_size, diff);
                }
            }
            (GroupChild::Group(g1), GroupChild::Group(g2)) => {
                if g1.size != g2.size {
                    let diff = (g2.size as i64) - (g1.size as i64);
                    total_diff += diff;
                    println!("    子GRUP {} ('{}'): {} vs {} (差异: {})", 
                        i, String::from_utf8_lossy(&g1.label), g1.size, g2.size, diff);
                }
            }
            _ => {
                println!("    ⚠️ 子元素 {} 类型不匹配!", i);
            }
        }
    }
    
    let grup_diff = (group2.size as i64) - (group1.size as i64);
    println!("    计算的总差异: {} 字节", total_diff);
    println!("    实际GRUP差异: {} 字节", grup_diff);
    
    if total_diff != grup_diff {
        println!("    ⚠️ 差异不匹配!可能存在其他问题");
    }
    
    Ok(())
}

/// 处理字符串文件解析
fn handle_string_file_parsing(cli: &Cli, string_file_path: &PathBuf) -> Result<(), Box<dyn std::error::Error>> {
    if !string_file_path.exists() {
        return Err(format!("字符串文件不存在: {:?}", string_file_path).into());
    }
    
    if !cli.quiet {
        println!("正在解析字符串文件: {:?}", string_file_path);
    }
    
    let string_file = StringFile::new(string_file_path.clone())?;
    
    if cli.stats {
        println!("{}", string_file.get_stats());
        return Ok(());
    }
    
    // 将字符串转换为JSON格式输出
    let entries: Vec<_> = string_file.entries.values().collect();
    let json_output = serde_json::to_string_pretty(&entries)
        .map_err(|e| format!("序列化JSON失败: {}", e))?;
    
    let output_path = cli.output.as_ref()
        .map(|p| p.clone())
        .unwrap_or_else(|| string_file_path.with_extension("json"));
    
    std::fs::write(&output_path, &json_output)
        .map_err(|e| format!("写入文件失败: {}", e))?;
    
    if !cli.quiet {
        println!("解析完成:");
        println!("  插件名: {}", string_file.plugin_name);
        println!("  语言: {}", string_file.language);
        println!("  文件类型: {:?}", string_file.file_type);
        println!("  字符串数量: {}", string_file.count());
        println!("  结果已写入: {:?}", output_path);
        
        // 显示前几个字符串样例
        let sample_entries: Vec<_> = string_file.entries.values().take(3).collect();
        if !sample_entries.is_empty() {
            println!("\n样例字符串:");
            for (i, entry) in sample_entries.iter().enumerate() {
                println!("{}. ID {}: \"{}\"", 
                    i + 1, 
                    entry.id,
                    if entry.content.chars().count() > 50 {
                        format!("{}...", entry.content.chars().take(50).collect::<String>())
                    } else {
                        entry.content.clone()
                    }
                );
            }
        }
    }
    
    Ok(())
}



#[cfg(not(feature = "cli"))]
fn main() {
    eprintln!("命令行工具功能未启用。请使用 --features cli 编译,或将此库用作依赖项。");
    std::process::exit(1);
}