diff --git a/BannerlordModEditor.Common.Tests/Services/TpacParserServiceTests.cs b/BannerlordModEditor.Common.Tests/Services/TpacParserServiceTests.cs new file mode 100644 index 00000000..87ef78f0 --- /dev/null +++ b/BannerlordModEditor.Common.Tests/Services/TpacParserServiceTests.cs @@ -0,0 +1,111 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using Xunit; +using BannerlordModEditor.Common.Services; + +namespace BannerlordModEditor.Common.Tests.Services +{ + public class TpacParserServiceTests + { + private readonly TpacParserService _service; + + public TpacParserServiceTests() + { + _service = new TpacParserService(); + } + + [Fact] + public void IsTpacFile_ReturnsTrueForTpacExtension() + { + Assert.True(_service.IsTpacFile("test.tpac")); + Assert.True(_service.IsTpacFile("path/to/file.TPAC")); + } + + [Fact] + public void IsTpacFile_ReturnsFalseForNonTpacExtension() + { + Assert.False(_service.IsTpacFile("test.xml")); + Assert.False(_service.IsTpacFile("test.txt")); + } + + [Fact] + public void ParseTpacFile_ReturnsInvalidForNonExistentFile() + { + var result = _service.ParseTpacFile("/nonexistent/file.tpac"); + Assert.False(result.IsValid); + Assert.Equal("File not found", result.ErrorMessage); + } + + [Fact] + public async Task ParseTpacFileAsync_ReturnsInvalidForNonExistentFile() + { + var result = await _service.ParseTpacFileAsync("/nonexistent/file.tpac"); + Assert.False(result.IsValid); + } + + [Fact] + public void ParseTpacFile_ExtractsModelsFromValidFile() + { + var tempFile = Path.GetTempFileName(); + var content = @"model_id: 12345 +model_name: TestModel +model_type: Character +property1: value1 +property2: value2 +model_id: 67890 +model_name: AnotherModel +model_type: Item"; + + try + { + File.WriteAllText(tempFile, content); + + var result = _service.ParseTpacFile(tempFile); + + Assert.True(result.IsValid); + Assert.Equal(2, result.Models.Count); + Assert.Equal("12345", result.Models[0].ModelId); + Assert.Equal("TestModel", result.Models[0].ModelName); + Assert.Equal("Character", result.Models[0].ModelType); + Assert.Equal("67890", result.Models[1].ModelId); + } + finally + { + File.Delete(tempFile); + } + } + + [Fact] + public async Task ParseTpacFileAsync_ExtractsModelsFromValidFile() + { + var tempFile = Path.GetTempFileName(); + var content = @"model_id: 99999 +model_name: AsyncModel +model_type: Weapon"; + + try + { + File.WriteAllText(tempFile, content); + + var result = await _service.ParseTpacFileAsync(tempFile); + + Assert.True(result.IsValid); + Assert.Single(result.Models); + Assert.Equal("99999", result.Models[0].ModelId); + Assert.Equal("AsyncModel", result.Models[0].ModelName); + } + finally + { + File.Delete(tempFile); + } + } + + [Fact] + public void ExtractModels_ReturnsEmptyForNonExistentFile() + { + var result = _service.ExtractModels("/nonexistent/file.tpac"); + Assert.Empty(result); + } + } +} diff --git a/BannerlordModEditor.Common/Services/TpacParserService.cs b/BannerlordModEditor.Common/Services/TpacParserService.cs new file mode 100644 index 00000000..f8c70606 --- /dev/null +++ b/BannerlordModEditor.Common/Services/TpacParserService.cs @@ -0,0 +1,143 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; + +namespace BannerlordModEditor.Common.Services +{ + public class TpacModelInfo + { + public string ModelId { get; set; } = string.Empty; + public string ModelName { get; set; } = string.Empty; + public string ModelType { get; set; } = string.Empty; + public Dictionary Properties { get; set; } = new(); + } + + public class TpacFileInfo + { + public string FilePath { get; set; } = string.Empty; + public string FileName { get; set; } = string.Empty; + public List Models { get; set; } = new(); + public bool IsValid { get; set; } + public string? ErrorMessage { get; set; } + } + + public interface ITpacParserService + { + Task ParseTpacFileAsync(string filePath); + TpacFileInfo ParseTpacFile(string filePath); + List ExtractModels(string tpacFilePath); + bool IsTpacFile(string filePath); + } + + public class TpacParserService : ITpacParserService + { + private const string TpacExtension = ".tpac"; + + public async Task ParseTpacFileAsync(string filePath) + { + return await Task.Run(() => ParseTpacFile(filePath)); + } + + public TpacFileInfo ParseTpacFile(string filePath) + { + var info = new TpacFileInfo + { + FilePath = filePath, + FileName = Path.GetFileName(filePath) + }; + + if (!File.Exists(filePath)) + { + info.IsValid = false; + info.ErrorMessage = "File not found"; + return info; + } + + if (!IsTpacFile(filePath)) + { + info.IsValid = false; + info.ErrorMessage = "Not a valid .tpac file"; + return info; + } + + try + { + info.Models = ExtractModels(filePath); + info.IsValid = true; + } + catch (Exception ex) + { + info.IsValid = false; + info.ErrorMessage = ex.Message; + } + + return info; + } + + public List ExtractModels(string tpacFilePath) + { + var models = new List(); + + if (!File.Exists(tpacFilePath)) + return models; + + try + { + var content = File.ReadAllText(tpacFilePath); + + var lines = content.Split('\n', StringSplitOptions.RemoveEmptyEntries); + var currentModel = new TpacModelInfo(); + + foreach (var line in lines) + { + var trimmed = line.Trim(); + + if (trimmed.StartsWith("model_id:", StringComparison.OrdinalIgnoreCase)) + { + if (!string.IsNullOrEmpty(currentModel.ModelId)) + { + models.Add(currentModel); + } + currentModel = new TpacModelInfo + { + ModelId = trimmed["model_id:".Length..].Trim() + }; + } + else if (trimmed.StartsWith("model_name:", StringComparison.OrdinalIgnoreCase)) + { + currentModel.ModelName = trimmed["model_name:".Length..].Trim(); + } + else if (trimmed.StartsWith("model_type:", StringComparison.OrdinalIgnoreCase)) + { + currentModel.ModelType = trimmed["model_type:".Length..].Trim(); + } + else if (trimmed.Contains(':')) + { + var parts = trimmed.Split(':', 2); + if (parts.Length == 2) + { + currentModel.Properties[parts[0].Trim()] = parts[1].Trim(); + } + } + } + + if (!string.IsNullOrEmpty(currentModel.ModelId)) + { + models.Add(currentModel); + } + } + catch + { + } + + return models; + } + + public bool IsTpacFile(string filePath) + { + return Path.GetExtension(filePath).Equals(TpacExtension, StringComparison.OrdinalIgnoreCase); + } + } +}