CsvExchange.cs 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Linq;
  5. using System.Text;
  6. using LocalizationTool.Models;
  7. namespace LocalizationTool.Services
  8. {
  9. /// <summary>
  10. /// CSV 导入导出(逗号分隔,Excel 可直接打开编辑)。
  11. /// 列结构与主界面表格一致:第一列"中文原文",其后为各语言代码列(en-US、ja-JP…)。
  12. /// 导出 UTF-8 带 BOM(Excel 中文不乱码);导入兼容 UTF-8 与 ANSI(GB18030)。
  13. /// </summary>
  14. public static class CsvExchange
  15. {
  16. /// <summary>导出表格行到 CSV 文件。langs 决定语言列(与表格当前勾选列一致)。</summary>
  17. public static void Export(string path, IEnumerable<RowVm> rows, List<PackFile> langs)
  18. {
  19. var sb = new StringBuilder();
  20. sb.AppendLine(JoinCsv(new[] { "中文原文" }.Concat(langs.Select(l => l.LanguageCode))));
  21. foreach (var r in rows)
  22. {
  23. var fields = new List<string> { r.Key };
  24. foreach (var l in langs)
  25. fields.Add(r.Cells.TryGetValue(l.LanguageCode, out var c) ? c.Value : "");
  26. sb.AppendLine(JoinCsv(fields));
  27. }
  28. File.WriteAllText(path, sb.ToString(), new UTF8Encoding(true)); // BOM:Excel 识别 UTF-8
  29. }
  30. /// <summary>解析 CSV 文件:返回 (表头, 数据行)。空行跳过;引号/逗号/换行转义按 RFC4180 处理。</summary>
  31. public static Tuple<List<string>, List<string[]>> Parse(string path)
  32. {
  33. var bytes = File.ReadAllBytes(path);
  34. if (bytes.Length >= 3 && bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF)
  35. bytes = bytes.Skip(3).ToArray();
  36. string text;
  37. try { text = new UTF8Encoding(false, true).GetString(bytes); }
  38. catch { text = Encoding.GetEncoding("gb18030").GetString(bytes); } // Excel 另存的 ANSI CSV
  39. var header = new List<string>();
  40. var records = new List<string[]>();
  41. using (var reader = new StringReader(text))
  42. using (var parser = new Microsoft.VisualBasic.FileIO.TextFieldParser(reader))
  43. {
  44. parser.TextFieldType = Microsoft.VisualBasic.FileIO.FieldType.Delimited;
  45. parser.SetDelimiters(",");
  46. parser.HasFieldsEnclosedInQuotes = true;
  47. while (!parser.EndOfData)
  48. {
  49. string[] fields;
  50. try { fields = parser.ReadFields(); }
  51. catch (Microsoft.VisualBasic.FileIO.MalformedLineException)
  52. {
  53. throw new Exception($"第 {parser.ErrorLineNumber} 行格式错误(常见原因:引号未闭合)");
  54. }
  55. if (fields == null) continue;
  56. if (fields.All(string.IsNullOrWhiteSpace)) continue;
  57. if (header.Count == 0) { header.AddRange(fields); continue; }
  58. records.Add(fields);
  59. }
  60. }
  61. return Tuple.Create(header, records);
  62. }
  63. private static string JoinCsv(IEnumerable<string> fields) =>
  64. string.Join(",", fields.Select(Escape));
  65. private static string Escape(string field)
  66. {
  67. field = field ?? "";
  68. if (field.IndexOfAny(new[] { ',', '"', '\r', '\n' }) >= 0)
  69. return "\"" + field.Replace("\"", "\"\"") + "\"";
  70. return field;
  71. }
  72. }
  73. }