diff --git a/.gitignore b/.gitignore
index 2dd2119..0682222 100644
--- a/.gitignore
+++ b/.gitignore
@@ -269,3 +269,4 @@ __pycache__/
/config/output_data
/config/output_lua
/config/output_lua_without_test
+/src/Excel2TextDiff/Properties/launchSettings.json
diff --git a/config/Datas/test/full_type.xlsx b/config/Datas/test/full_type.xlsx
index bb5374a..fee10bc 100644
Binary files a/config/Datas/test/full_type.xlsx and b/config/Datas/test/full_type.xlsx differ
diff --git a/src/Excel2TextDiff/Excel2TextDiff.csproj b/src/Excel2TextDiff/Excel2TextDiff.csproj
new file mode 100644
index 0000000..9503e57
--- /dev/null
+++ b/src/Excel2TextDiff/Excel2TextDiff.csproj
@@ -0,0 +1,13 @@
+
+
+
+ Exe
+ net5.0
+
+
+
+
+
+
+
+
diff --git a/src/Excel2TextDiff/Excel2TextWriter.cs b/src/Excel2TextDiff/Excel2TextWriter.cs
new file mode 100644
index 0000000..ac5336c
--- /dev/null
+++ b/src/Excel2TextDiff/Excel2TextWriter.cs
@@ -0,0 +1,61 @@
+using CommandLine;
+using ExcelDataReader;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Excel2TextDiff
+{
+ class Excel2TextWriter
+ {
+ class CommandLineOptions
+ {
+ [Option('p', "port", Required = false, HelpText = "listen port")]
+ public int Port { get; set; } = 8899;
+ }
+
+ public void TransformToTextAndSave(string excelFile, string outputTextFile)
+ {
+ var lines = new List();
+ using var excelFileStream = new FileStream(excelFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
+ string ext = Path.GetExtension(excelFile);
+ using (var reader = ext != ".csv" ? ExcelReaderFactory.CreateReader(excelFileStream) : ExcelReaderFactory.CreateCsvReader(excelFileStream))
+ {
+ do
+ {
+ lines.Add($"===[{reader.Name ?? ""}]===");
+ LoadRemainRows(reader, lines);
+ } while (reader.NextResult());
+ }
+ File.WriteAllLines(outputTextFile, lines, System.Text.Encoding.UTF8);
+ }
+
+ private void LoadRemainRows(IExcelDataReader reader, List lines)
+ {
+ int rowIndex = 0;
+ while (reader.Read())
+ {
+ ++rowIndex; // 第一行是 meta ,跳过
+ var row = new List();
+ for (int i = 0, n = reader.FieldCount; i < n; i++)
+ {
+ object cell = reader.GetValue(i);
+ row.Add(cell != null ? cell.ToString() : "");
+ }
+ int lastNotEmptyIndex = row.FindLastIndex(s => !string.IsNullOrEmpty(s));
+ if (lastNotEmptyIndex >= 0)
+ {
+ row = row.GetRange(0, lastNotEmptyIndex + 1);
+ }
+ else
+ {
+ row.Clear();
+ }
+ lines.Add(string.Join(',', row));
+ }
+ }
+ }
+}
diff --git a/src/Excel2TextDiff/Program.cs b/src/Excel2TextDiff/Program.cs
new file mode 100644
index 0000000..4cd89f7
--- /dev/null
+++ b/src/Excel2TextDiff/Program.cs
@@ -0,0 +1,90 @@
+using CommandLine;
+using CommandLine.Text;
+using System;
+using System.Collections.Generic;
+using System.IO;
+
+namespace Excel2TextDiff
+{
+ class CommandLineOptions
+ {
+ [Option('t', SetName = "transform", HelpText = "transform excel to text file")]
+ public bool IsTransform { get; set; }
+
+ [Option('d', SetName = "diff", HelpText = "transform and diff file")]
+ public bool IsDiff { get; set; }
+
+ [Option('p', SetName = "diff", Required = false, HelpText = "3rd diff program. default TortoiseMerge")]
+ public string DiffProgram { get; set; }
+
+ [Value(0)]
+ public IList Files { get; set; }
+ }
+
+ class Program
+ {
+ static void Main(string[] args)
+ {
+ //if (args.Length != 2 && args.Length != 3)
+ //{
+ // Console.WriteLine("Usage:");
+ // Console.WriteLine("Excel2TextDiff [path_of_diff_program]");
+ // return;
+ //}
+
+ var options = ParseOptions(args);
+
+ System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance);
+ var writer = new Excel2TextWriter();
+
+ if (options.IsTransform)
+ {
+ if (options.Files.Count != 2)
+ {
+ Console.WriteLine("Usage: Excel2TextDiff -t ");
+ Environment.Exit(1);
+ }
+
+ writer.TransformToTextAndSave(options.Files[0], options.Files[1]);
+ }
+ else
+ {
+ if (options.Files.Count != 2)
+ {
+ Console.WriteLine("Usage: Excel2TextDiff -d [-p ]");
+ Environment.Exit(1);
+ }
+
+ var diffProgame = options.DiffProgram ?? "TortoiseMerge.exe";
+
+ var tempTxt1 = Path.GetTempFileName();
+ writer.TransformToTextAndSave(options.Files[0], tempTxt1);
+
+ var tempTxt2 = Path.GetTempFileName();
+ writer.TransformToTextAndSave(options.Files[1], tempTxt2);
+
+ string arg1 = $"/base:{tempTxt1.Replace('\\', '/')}";
+ string arg2 = $"/mine:{tempTxt2.Replace('\\', '/')}";
+ Console.WriteLine(" {0} {1}", arg1, arg2);
+ System.Diagnostics.Process.Start(diffProgame, new string[] { arg1, arg2 });
+ }
+ }
+
+ private static CommandLineOptions ParseOptions(String[] args)
+ {
+ var helpWriter = new StringWriter();
+ var parser = new Parser(ps =>
+ {
+ ps.HelpWriter = helpWriter;
+ });
+
+ var result = parser.ParseArguments(args);
+ if (result.Tag == ParserResultType.NotParsed)
+ {
+ Console.Error.WriteLine(helpWriter.ToString());
+ Environment.Exit(1);
+ }
+ return ((Parsed)result).Value;
+ }
+ }
+}
diff --git a/src/Luban.sln b/src/Luban.sln
index d1ea460..2afa966 100644
--- a/src/Luban.sln
+++ b/src/Luban.sln
@@ -19,6 +19,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Luban.Job.Proto", "Luban.Jo
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Luban.Job.Db", "Luban.Job.Db\Luban.Job.Db.csproj", "{7467AC15-C61F-4C56-942F-18EAEA902C58}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Excel2TextDiff", "Excel2TextDiff\Excel2TextDiff.csproj", "{9477226F-469E-458F-A3AD-9115D777A65A}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -57,6 +59,10 @@ Global
{7467AC15-C61F-4C56-942F-18EAEA902C58}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7467AC15-C61F-4C56-942F-18EAEA902C58}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7467AC15-C61F-4C56-942F-18EAEA902C58}.Release|Any CPU.Build.0 = Release|Any CPU
+ {9477226F-469E-458F-A3AD-9115D777A65A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {9477226F-469E-458F-A3AD-9115D777A65A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {9477226F-469E-458F-A3AD-9115D777A65A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {9477226F-469E-458F-A3AD-9115D777A65A}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE