diff --git a/OpenKh.Tools.Kh2TextEditor/OpenKh.Tools.Kh2TextEditor.csproj b/OpenKh.Tools.Kh2TextEditor/OpenKh.Tools.Kh2TextEditor.csproj index b3cc2d242..cdf91855a 100644 --- a/OpenKh.Tools.Kh2TextEditor/OpenKh.Tools.Kh2TextEditor.csproj +++ b/OpenKh.Tools.Kh2TextEditor/OpenKh.Tools.Kh2TextEditor.csproj @@ -10,6 +10,10 @@ ..\bin\$(Configuration)\ true + + + + diff --git a/OpenKh.Tools.Kh2TextEditor/Services/CsvTextExporter.cs b/OpenKh.Tools.Kh2TextEditor/Services/CsvTextExporter.cs new file mode 100644 index 000000000..f6ef5749b --- /dev/null +++ b/OpenKh.Tools.Kh2TextEditor/Services/CsvTextExporter.cs @@ -0,0 +1,27 @@ +using CsvHelper; +using CsvHelper.Configuration; +using OpenKh.Tools.Kh2TextEditor.Models; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OpenKh.Tools.Kh2TextEditor.Services +{ + class CsvTextExporter : ITextExporter + { + void ITextExporter.Export(IEnumerable messages, TextWriter writer) + { + new CsvWriter(writer, new CsvConfiguration(CultureInfo.InvariantCulture) + { + Delimiter = "," + }) + .WriteRecords(messages); + } + + (string, string[]) ITextExporter.Filter() => ("CSV", "csv".Split(';')); + } +} diff --git a/OpenKh.Tools.Kh2TextEditor/Services/CsvTextImporter.cs b/OpenKh.Tools.Kh2TextEditor/Services/CsvTextImporter.cs new file mode 100644 index 000000000..5e04be073 --- /dev/null +++ b/OpenKh.Tools.Kh2TextEditor/Services/CsvTextImporter.cs @@ -0,0 +1,27 @@ +using CsvHelper; +using CsvHelper.Configuration; +using OpenKh.Tools.Kh2TextEditor.Models; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OpenKh.Tools.Kh2TextEditor.Services +{ + class CsvTextImporter : ITextImporter + { + (string, string[]) ITextImporter.Filter() => ("CSV", "csv".Split(';')); + + IEnumerable ITextImporter.Import(TextReader reader) + { + return new CsvReader(reader, new CsvConfiguration(CultureInfo.InvariantCulture) + { + Delimiter = ",", + }) + .GetRecords(); + } + } +} diff --git a/OpenKh.Tools.Kh2TextEditor/Services/ExchangeableMessage.cs b/OpenKh.Tools.Kh2TextEditor/Services/ExchangeableMessage.cs new file mode 100644 index 000000000..f28ec2848 --- /dev/null +++ b/OpenKh.Tools.Kh2TextEditor/Services/ExchangeableMessage.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Xml.Serialization; + +namespace OpenKh.Tools.Kh2TextEditor.Services +{ + public class ExchangeableMessage + { + [XmlElement] + public int Id { get; set; } + [XmlElement] + public string Text { get; set; } + } +} diff --git a/OpenKh.Tools.Kh2TextEditor/Services/ITextExporter.cs b/OpenKh.Tools.Kh2TextEditor/Services/ITextExporter.cs new file mode 100644 index 000000000..e9f3c13d7 --- /dev/null +++ b/OpenKh.Tools.Kh2TextEditor/Services/ITextExporter.cs @@ -0,0 +1,16 @@ +using OpenKh.Tools.Kh2TextEditor.Models; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OpenKh.Tools.Kh2TextEditor.Services +{ + public interface ITextExporter + { + void Export(IEnumerable messages, TextWriter writer); + (string, string[]) Filter(); + } +} diff --git a/OpenKh.Tools.Kh2TextEditor/Services/ITextImporter.cs b/OpenKh.Tools.Kh2TextEditor/Services/ITextImporter.cs new file mode 100644 index 000000000..7a113c737 --- /dev/null +++ b/OpenKh.Tools.Kh2TextEditor/Services/ITextImporter.cs @@ -0,0 +1,16 @@ +using OpenKh.Tools.Kh2TextEditor.Models; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OpenKh.Tools.Kh2TextEditor.Services +{ + public interface ITextImporter + { + IEnumerable Import(TextReader reader); + (string, string[]) Filter(); + } +} diff --git a/OpenKh.Tools.Kh2TextEditor/Services/PlainTextExporter.cs b/OpenKh.Tools.Kh2TextEditor/Services/PlainTextExporter.cs new file mode 100644 index 000000000..43475ae5c --- /dev/null +++ b/OpenKh.Tools.Kh2TextEditor/Services/PlainTextExporter.cs @@ -0,0 +1,24 @@ +using OpenKh.Tools.Kh2TextEditor.Models; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OpenKh.Tools.Kh2TextEditor.Services +{ + class PlainTextExporter : ITextExporter + { + void ITextExporter.Export(IEnumerable messages, TextWriter writer) + { + foreach (var one in messages) + { + writer.WriteLine($"{one.Id}: {one.Text}"); + writer.WriteLine("---"); + } + } + + (string, string[]) ITextExporter.Filter() => ("Plain text", "txt".Split(';')); + } +} diff --git a/OpenKh.Tools.Kh2TextEditor/Services/TextExporters.cs b/OpenKh.Tools.Kh2TextEditor/Services/TextExporters.cs new file mode 100644 index 000000000..2ee173bc4 --- /dev/null +++ b/OpenKh.Tools.Kh2TextEditor/Services/TextExporters.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OpenKh.Tools.Kh2TextEditor.Services +{ + public class TextExporters + { + public static IEnumerable GetAll() => new ITextExporter[] + { + new PlainTextExporter(), + new CsvTextExporter(), + new XmlTextExporter(), + new YamlTextExporter(), + }; + + public static ITextExporter FindFromFile(string fileName) + { + var selectedExtension = Path.GetExtension(fileName).TrimStart('.'); + var textExporters = GetAll(); + + return textExporters + .Where( + exporter => exporter.Filter().Item2 + .Any( + it => string.Compare(it, selectedExtension, true) == 0 + ) + ) + .FirstOrDefault() ?? textExporters.First(); // fallback + } + } +} diff --git a/OpenKh.Tools.Kh2TextEditor/Services/TextImporters.cs b/OpenKh.Tools.Kh2TextEditor/Services/TextImporters.cs new file mode 100644 index 000000000..874d00dce --- /dev/null +++ b/OpenKh.Tools.Kh2TextEditor/Services/TextImporters.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OpenKh.Tools.Kh2TextEditor.Services +{ + public class TextImporters + { + public static IEnumerable GetAll() => new ITextImporter[] + { + new CsvTextImporter(), + new XmlTextImporter(), + new YamlTextImporter(), + }; + + public static ITextImporter FindFromFile(string fileName) + { + var selectedExtension = Path.GetExtension(fileName).TrimStart('.'); + var textExporters = GetAll(); + + return textExporters + .Where( + exporter => exporter.Filter().Item2 + .Any( + it => string.Compare(it, selectedExtension, true) == 0 + ) + ) + .FirstOrDefault(); + } + } +} diff --git a/OpenKh.Tools.Kh2TextEditor/Services/XmlTextExporter.cs b/OpenKh.Tools.Kh2TextEditor/Services/XmlTextExporter.cs new file mode 100644 index 000000000..5e2189bb9 --- /dev/null +++ b/OpenKh.Tools.Kh2TextEditor/Services/XmlTextExporter.cs @@ -0,0 +1,34 @@ +using OpenKh.Tools.Kh2TextEditor.Models; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Xml.Serialization; + +namespace OpenKh.Tools.Kh2TextEditor.Services +{ + public class XmlTextExporter : ITextExporter + { + void ITextExporter.Export(IEnumerable messages, TextWriter writer) + { + new XmlSerializer(typeof(RootModel)).Serialize( + writer, + new RootModel + { + Message = messages.ToArray() + } + ); + } + + (string, string[]) ITextExporter.Filter() => ("XML", "xml".Split(';')); + + [XmlRoot("Messages")] + public class RootModel + { + [XmlElement] + public ExchangeableMessage[] Message { get; set; } + } + } +} diff --git a/OpenKh.Tools.Kh2TextEditor/Services/XmlTextImporter.cs b/OpenKh.Tools.Kh2TextEditor/Services/XmlTextImporter.cs new file mode 100644 index 000000000..236ceb8bf --- /dev/null +++ b/OpenKh.Tools.Kh2TextEditor/Services/XmlTextImporter.cs @@ -0,0 +1,29 @@ +using OpenKh.Tools.Kh2TextEditor.Models; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Xml.Serialization; + +namespace OpenKh.Tools.Kh2TextEditor.Services +{ + public class XmlTextImporter : ITextImporter + { + (string, string[]) ITextImporter.Filter() => ("XML", "xml".Split(';')); + + IEnumerable ITextImporter.Import(TextReader reader) + { + var model = (RootModel)new XmlSerializer(typeof(RootModel)).Deserialize(reader); + return model.Message; + } + + [XmlRoot("Messages")] + public class RootModel + { + [XmlElement] + public ExchangeableMessage[] Message { get; set; } + } + } +} diff --git a/OpenKh.Tools.Kh2TextEditor/Services/YamlTextExporter.cs b/OpenKh.Tools.Kh2TextEditor/Services/YamlTextExporter.cs new file mode 100644 index 000000000..11ac79b00 --- /dev/null +++ b/OpenKh.Tools.Kh2TextEditor/Services/YamlTextExporter.cs @@ -0,0 +1,28 @@ +using OpenKh.Tools.Kh2TextEditor.Models; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OpenKh.Tools.Kh2TextEditor.Services +{ + class YamlTextExporter : ITextExporter + { + void ITextExporter.Export(IEnumerable messages, TextWriter writer) + { + new YamlDotNet.Serialization.SerializerBuilder() + .Build() + .Serialize( + writer, + new + { + Messages = messages + } + ); + } + + (string, string[]) ITextExporter.Filter() => ("YAML", "yml".Split(';')); + } +} diff --git a/OpenKh.Tools.Kh2TextEditor/Services/YamlTextImporter.cs b/OpenKh.Tools.Kh2TextEditor/Services/YamlTextImporter.cs new file mode 100644 index 000000000..862fef399 --- /dev/null +++ b/OpenKh.Tools.Kh2TextEditor/Services/YamlTextImporter.cs @@ -0,0 +1,29 @@ +using OpenKh.Tools.Kh2TextEditor.Models; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OpenKh.Tools.Kh2TextEditor.Services +{ + class YamlTextImporter : ITextImporter + { + (string, string[]) ITextImporter.Filter() => ("YAML", "yml".Split(';')); + + IEnumerable ITextImporter.Import(TextReader reader) + { + var model = new YamlDotNet.Serialization.DeserializerBuilder() + .Build() + .Deserialize(reader); + + return model.Messages; + } + + public class RootModel + { + public ExchangeableMessage[] Messages { get; set; } + } + } +} diff --git a/OpenKh.Tools.Kh2TextEditor/ViewModels/MainViewModel.cs b/OpenKh.Tools.Kh2TextEditor/ViewModels/MainViewModel.cs index 14382948b..b429867e0 100644 --- a/OpenKh.Tools.Kh2TextEditor/ViewModels/MainViewModel.cs +++ b/OpenKh.Tools.Kh2TextEditor/ViewModels/MainViewModel.cs @@ -16,7 +16,9 @@ using Xe.Tools; using Xe.Tools.Wpf.Commands; using Xe.Tools.Wpf.Dialogs; - +using OpenKh.Tools.Kh2TextEditor.Services; +using System.Text; + namespace OpenKh.Tools.Kh2TextEditor.ViewModels { public class MainViewModel : BaseNotifyPropertyChanged @@ -46,6 +48,9 @@ private string FileName public RelayCommand OpenCommand { get; } public RelayCommand SaveCommand { get; } public RelayCommand SaveAsCommand { get; } + public RelayCommand ExportMessageAsCommand { get; } + public RelayCommand ImportMessageFromCommand { get; } + public RelayCommand ExitCommand { get; } public RelayCommand GuideCommand { get; } public RelayCommand AboutCommand { get; } @@ -118,6 +123,48 @@ public MainViewModel() } }, x => true); + ExportMessageAsCommand = new RelayCommand(x => + { + var fd = FileDialog.Factory(Window, FileDialog.Behavior.Save, + TextExporters.GetAll().Select(exporter => exporter.Filter()) + ); + + if (fd.ShowDialog() == true) + { + var selectedExtension = $"{Path.GetExtension(fd.FileName).TrimStart('.')}"; + + ExportMessageAsFile( + fileName: fd.FileName, + textExporter: TextExporters.FindFromFile(fd.FileName) + ); + } + }, x => true); + + ImportMessageFromCommand = new RelayCommand(x => + { + var fd = FileDialog.Factory(Window, FileDialog.Behavior.Open, + TextImporters.GetAll().Select(importer => importer.Filter()) + ); + + if (fd.ShowDialog() == true) + { + var selectedExtension = $"{Path.GetExtension(fd.FileName).TrimStart('.')}"; + + var textImporter = TextImporters.FindFromFile(fd.FileName); + if (textImporter != null) + { + ImportMessageFromFile( + fileName: fd.FileName, + textImporter + ); + } + else + { + MessageBox.Show($"Failed to match text decoder for your file:\n{fd.FileName}"); + } + } + }, x => true); + ExitCommand = new RelayCommand(x => { Window.Close(); @@ -185,7 +232,7 @@ public MainViewModel() }, x => true); AboutCommand = new RelayCommand(x => - { + { new AboutDialog(Assembly.GetExecutingAssembly()).ShowDialog(); }, x => true); @@ -239,6 +286,40 @@ public void SaveFile(string previousFileName, string fileName) } } + public void ExportMessageAsFile(string fileName, ITextExporter textExporter) + { + new StreamWriter(fileName, false, Encoding.UTF8).Using( + writer => textExporter.Export( + TextEditor.Messages + .Select( + source => new ExchangeableMessage + { + Id = source.Id, + Text = source.Text, + } + ), + writer + ) + ); + } + + public void ImportMessageFromFile(string fileName, ITextImporter textImporter) + { + var importedMessages = new StreamReader(fileName, Encoding.UTF8).Using( + reader => textImporter.Import(reader) + .ToArray() // make sure to import all messages from file before closing StreamReader! + ); + + foreach (var importMessage in importedMessages) + { + var found = TextEditor.Messages.SingleOrDefault(it => it.Id == importMessage.Id); + if (found != null) + { + found.Text = importMessage.Text; + } + } + } + private void OpenFontImageFile(string fileName) => File.OpenRead(fileName).Using(stream => { if (Bar.IsValid(stream)) diff --git a/OpenKh.Tools.Kh2TextEditor/Views/MainWindow.xaml b/OpenKh.Tools.Kh2TextEditor/Views/MainWindow.xaml index aaa2c1f21..0724023a6 100644 --- a/OpenKh.Tools.Kh2TextEditor/Views/MainWindow.xaml +++ b/OpenKh.Tools.Kh2TextEditor/Views/MainWindow.xaml @@ -26,6 +26,11 @@ + + +