diff --git a/StarWars5e.Api/Controllers/FightingStrategyController.cs b/StarWars5e.Api/Controllers/FightingStrategyController.cs new file mode 100644 index 0000000..c19d077 --- /dev/null +++ b/StarWars5e.Api/Controllers/FightingStrategyController.cs @@ -0,0 +1,53 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Azure.Cosmos.Table; +using StarWars5e.Api.Storage; +using StarWars5e.Models; +using StarWars5e.Models.Enums; + +namespace StarWars5e.Api.Controllers +{ + [Route("api/[controller]")] + [ApiController] + public class FightingStrategyController : ControllerBase + { + private readonly IAzureTableStorage _tableStorage; + + public FightingStrategyController(IAzureTableStorage tableStorage) + { + _tableStorage = tableStorage; + } + + [HttpGet] + public async Task>> Get(Language language = Language.en) + { + List FightingStrategy; + try + { + FightingStrategy = (await _tableStorage.GetAllAsync($"fightingStrategies{language}")).ToList(); + } + catch (StorageException e) + { + if (e.Message == "Not Found") + { + FightingStrategy = (await _tableStorage.GetAllAsync($"fightingStrategies{Language.en}")).ToList(); + return Ok(FightingStrategy); + } + throw; + } + return Ok(FightingStrategy); + } + + //[HttpPost] + //public void Post([FromBody] Feat feat) + //{ + //} + + //[HttpDelete("{name}")] + //public void Delete(string name) + //{ + //} + } +} diff --git a/StarWars5e.Api/Controllers/LanguagesController.cs b/StarWars5e.Api/Controllers/LanguagesController.cs new file mode 100644 index 0000000..1a76dd3 --- /dev/null +++ b/StarWars5e.Api/Controllers/LanguagesController.cs @@ -0,0 +1,43 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Azure.Cosmos.Table; +using StarWars5e.Api.Storage; +using StarWars5e.Models; +using StarWars5e.Models.Enums; + +namespace StarWars5e.Api.Controllers +{ + [Route("api/[controller]")] + [ApiController] + public class LanguagesController : ControllerBase + { + private readonly IAzureTableStorage _tableStorage; + + public LanguagesController(IAzureTableStorage tableStorage) + { + _tableStorage = tableStorage; + } + + [HttpGet] + public async Task>> Get(Language language = Language.en) + { + List languages; + try + { + languages = (await _tableStorage.GetAllAsync($"languages{language}")).ToList(); + } + catch (StorageException e) + { + if (e.Message == "Not Found") + { + languages = (await _tableStorage.GetAllAsync($"languages{Language.en}")).ToList(); + return Ok(languages); + } + throw; + } + return Ok(languages); + } + } +} diff --git a/StarWars5e.Api/Startup.cs b/StarWars5e.Api/Startup.cs index 4797e31..3c25eb3 100644 --- a/StarWars5e.Api/Startup.cs +++ b/StarWars5e.Api/Startup.cs @@ -86,12 +86,13 @@ public void ConfigureServices(IServiceCollection services) }); }); + var searchEndpoint = Configuration["SearchEndpoint"] ?? "https://sw5esearch.search.windows.net"; var tableStorage = new AzureTableStorage(Configuration["StorageAccountConnectionString"]); var cloudStorageAccount = CloudStorageAccount.Parse(Configuration["StorageAccountConnectionString"]); var cloudTableClient = cloudStorageAccount.CreateCloudTableClient(); var cloudBlobClient = new BlobServiceClient(Configuration["StorageAccountConnectionString"]); - var searchIndexClient = new SearchIndexClient(new Uri("https://sw5esearch.search.windows.net"), new AzureKeyCredential(Configuration["SearchKey"])); - var searchClient = new SearchClient(new Uri("https://sw5esearch.search.windows.net"), "searchterms-index", new AzureKeyCredential(Configuration["SearchKey"])); + var searchIndexClient = new SearchIndexClient(new Uri(searchEndpoint), new AzureKeyCredential(Configuration["SearchKey"])); + var searchClient = new SearchClient(new Uri(searchEndpoint), "searchterms-index", new AzureKeyCredential(Configuration["SearchKey"])); services.AddSingleton(tableStorage); diff --git a/StarWars5e.Models/CommonLanguage.cs b/StarWars5e.Models/CommonLanguage.cs new file mode 100644 index 0000000..8416da7 --- /dev/null +++ b/StarWars5e.Models/CommonLanguage.cs @@ -0,0 +1,10 @@ +namespace StarWars5e.Models +{ + public class CommonLanguage : BaseEntity + { + public string Name { get; set; } + public string Text { get; set; } + public string Metadata { get; set; } + } + +} diff --git a/StarWars5e.Models/Enums/GlobalSearchTermType.cs b/StarWars5e.Models/Enums/GlobalSearchTermType.cs index 27d1f5f..6c039bf 100644 --- a/StarWars5e.Models/Enums/GlobalSearchTermType.cs +++ b/StarWars5e.Models/Enums/GlobalSearchTermType.cs @@ -56,6 +56,7 @@ public enum GlobalSearchTermType SplashclassImprovement, WeaponFocus, WeaponSupremacy, - Maneuver + Maneuver, + FightingStrategy } } diff --git a/StarWars5e.Models/Feat.cs b/StarWars5e.Models/Feat.cs index ff75dcd..d0e6fa3 100644 --- a/StarWars5e.Models/Feat.cs +++ b/StarWars5e.Models/Feat.cs @@ -5,9 +5,11 @@ namespace StarWars5e.Models { public class Feat : BaseEntity { + public Feat() { } public string Name { get; set; } public string Prerequisite { get; set; } public string Text { get; set; } + public string Metadata { get; set; } public List AttributesIncreased { get; set; } public string AttributesIncreasedJson { diff --git a/StarWars5e.Models/FightingStrategy.cs b/StarWars5e.Models/FightingStrategy.cs new file mode 100644 index 0000000..8887741 --- /dev/null +++ b/StarWars5e.Models/FightingStrategy.cs @@ -0,0 +1,9 @@ +namespace StarWars5e.Models +{ + public class FightingStrategy : BaseEntity + { + public string Name { get; set; } + public string Text { get; set; } + public string Metadata { get; set; } + } +} diff --git a/StarWars5e.Models/Utils/StringExtentions.cs b/StarWars5e.Models/Utils/StringExtentions.cs index 3e02737..adf5958 100644 --- a/StarWars5e.Models/Utils/StringExtentions.cs +++ b/StarWars5e.Models/Utils/StringExtentions.cs @@ -1,12 +1,31 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; +using System.Text.Encodings.Web; +using System.Text.Json; +using System.Text; using System.Text.RegularExpressions; namespace StarWars5e.Models.Utils { public static class StringExtentions { + public static string MinifyJson(this string json) + { + var options = + new JsonWriterOptions + { + Indented = false, + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping + }; + using var document = JsonDocument.Parse(json); + using var stream = new MemoryStream(); + using var writer = new Utf8JsonWriter(stream, options); + document.WriteTo(writer); + writer.Flush(); + return Encoding.UTF8.GetString(stream.ToArray()); + } public static bool HasLeadingHtmlWhitespace(this string input) { return input.Trim().StartsWith(" ") || input.Trim().StartsWith(" "); diff --git a/StarWars5e.Parser/Managers/PlayerHandbookManager.cs b/StarWars5e.Parser/Managers/PlayerHandbookManager.cs index f7ca65d..b17bf21 100644 --- a/StarWars5e.Parser/Managers/PlayerHandbookManager.cs +++ b/StarWars5e.Parser/Managers/PlayerHandbookManager.cs @@ -512,7 +512,20 @@ await _tableStorage.AddBatchAsync($"armorProperties{_localization Console.WriteLine("Failed to upload PHB armor properties."); } + try + { + var languages = await new PlayerHandbookLanguagesProcessor() + .Process(_phbFilesNames.Where(p => p.Equals("PHB.phb_04.txt")).ToList(), _localization); + await _tableStorage.AddBatchAsync($"languages{_localization.Language}", languages, + new BatchOperationOptions { BatchInsertMethod = BatchInsertMethod.InsertOrReplace }); + } + catch (StorageException) + { + Console.WriteLine("Failed to upload Common Languages."); + } + + try { var maneuvers = @@ -536,6 +549,30 @@ await _tableStorage.AddBatchAsync($"maneuvers{_localization.Language}" Console.WriteLine("Failed to upload PHB maneuvers."); } + + try + { + var fightingStrategies = + await new ExpandedContentFightingStrategiesProcessor(_localization).Process(_phbFilesNames.Where(p => p.Equals("PHB.phb_03.txt")).ToList(), _localization, ContentType.Core); + + foreach (var strategy in fightingStrategies) + { + strategy.ContentSourceEnum = ContentSource.PHB; + + var strategySearchTerm = _globalSearchTermRepository.CreateSearchTerm(strategy.Name, + GlobalSearchTermType.FightingStrategy, ContentType.Core, + $"/classes/fighter"); + _globalSearchTermRepository.SearchTerms.Add(strategySearchTerm); + } + + await _tableStorage.AddBatchAsync($"fightingStrategies{_localization.Language}", fightingStrategies, + new BatchOperationOptions { BatchInsertMethod = BatchInsertMethod.InsertOrReplace }); + } + catch (StorageException e) + { + Console.WriteLine("Failed to upload PHB fighting strategies."); + } + foreach (var referenceName in ReferenceNames) { var referenceSearchTerm = _globalSearchTermRepository.CreateSearchTerm(referenceName.name, referenceName.globalSearchTermType, ContentType.Core, diff --git a/StarWars5e.Parser/Processors/ExpandedContentFightingStrategiesProcessor.cs b/StarWars5e.Parser/Processors/ExpandedContentFightingStrategiesProcessor.cs new file mode 100644 index 0000000..ff61948 --- /dev/null +++ b/StarWars5e.Parser/Processors/ExpandedContentFightingStrategiesProcessor.cs @@ -0,0 +1,69 @@ +using StarWars5e.Models; +using StarWars5e.Models.Enums; +using StarWars5e.Models.Utils; +using StarWars5e.Parser.Localization; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace StarWars5e.Parser.Processors +{ + public class ExpandedContentFightingStrategiesProcessor : BaseProcessor + { + public ExpandedContentFightingStrategiesProcessor(ILocalization localization) + { + Localization = localization; + } + + public override Task> FindBlocks(List lines, ContentType contentType) + { + var strategies = new List(); + lines = lines.CleanListOfStrings().ToList(); + var strategyStartLines = lines.Where(f => f.StartsWith($"#### ") && f.EndsWith($"Strategist")); + + foreach (var strategyStartLine in strategyStartLines) + { + var strategyStartIndex = lines.IndexOf(strategyStartLine); + + var strategyEndIndex = lines.FindIndex(strategyStartIndex + 1, f => f.StartsWith("#")); + var strategyLines = lines.Skip(strategyStartIndex); + + if (strategyEndIndex != -1) + { + strategyLines = lines.Skip(strategyStartIndex).Take(strategyEndIndex - strategyStartIndex); + } + + strategies.Add(ParseStrategy(strategyLines.CleanListOfStrings().ToList(), contentType)); + } + + return Task.FromResult(strategies); + } + + public FightingStrategy ParseStrategy(List strategyLines, ContentType contentType) + { + var name = strategyLines[0].Split("####")[1].Trim(); + + try + { + var strategy = new FightingStrategy + { + RowKey = name.FormatKey(), + Name = name, + PartitionKey = contentType.ToString(), + ContentTypeEnum = contentType + }; + + strategy.Metadata = MetadataProcessor.ProcessMetadata(strategy); + strategy.Text = string.Join(Environment.NewLine, strategyLines.Skip(1)); + + return strategy; + } + catch (Exception e) + { + throw new Exception($"Failed while parsing fighting strategy {name}", e); + } + } + } +} diff --git a/StarWars5e.Parser/Processors/ExpandedContentSpeciesProcessor.cs b/StarWars5e.Parser/Processors/ExpandedContentSpeciesProcessor.cs index 8f0f0ec..6805f2a 100644 --- a/StarWars5e.Parser/Processors/ExpandedContentSpeciesProcessor.cs +++ b/StarWars5e.Parser/Processors/ExpandedContentSpeciesProcessor.cs @@ -113,6 +113,7 @@ public Species ParseSpecies(List speciesLines, ContentType contentType) foreach (var speciesTrait in species.Traits) { + //var metadata = ReadMetadata($"features.{Enum.GetName(FeatureSource.Species)}.{name.Replace(" ", "")}.{speciesTrait.Name.Replace(" ", "")}"); var feature = new Feature { Name = speciesTrait.Name, @@ -122,6 +123,7 @@ public Species ParseSpecies(List speciesLines, ContentType contentType) SourceEnum = FeatureSource.Species, PartitionKey = contentType.ToString() }; + feature.Metadata = MetadataProcessor.ProcessMetadata(feature); species.Features.Add(feature); } diff --git a/StarWars5e.Parser/Processors/FeatureMetadataProcessor.cs b/StarWars5e.Parser/Processors/FeatureMetadataProcessor.cs new file mode 100644 index 0000000..6ae945c --- /dev/null +++ b/StarWars5e.Parser/Processors/FeatureMetadataProcessor.cs @@ -0,0 +1,74 @@ +using StarWars5e.Models; +using StarWars5e.Models.Enums; +using StarWars5e.Models.Utils; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace StarWars5e.Parser.Processors +{ + internal static class MetadataProcessor + { + + public static string ProcessMetadata(Feature feature) + { + var manifestStreams = Assembly.GetExecutingAssembly().GetManifestResourceNames(); + var key = $"StarWars5e.Parser.Sources.metadata.features.{feature.Source}.{feature.SourceName.Replace(",", "").Replace(" ", "")}.{feature.Name.Replace(",", "").Replace(" ", "")}.json"; + key = manifestStreams.FirstOrDefault(s => s.Equals(key, StringComparison.OrdinalIgnoreCase)); + if (key == null) return String.Empty; + using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(key)) + { + if (stream == null) + { + return String.Empty; + } + using (var reader = new StreamReader(stream, Encoding.UTF8, true, 128)) + { + return reader.ReadToEnd().MinifyJson(); + } + } + } + + public static string ProcessMetadata(FightingStrategy fightingStrategy) + { + var manifestStreams = Assembly.GetExecutingAssembly().GetManifestResourceNames(); + var key = $"StarWars5e.Parser.Sources.metadata.FightingStrategy.{fightingStrategy.Name.Replace(",", "").Replace(" ", "")}.json"; + key = manifestStreams.FirstOrDefault(s => s.Equals(key, StringComparison.OrdinalIgnoreCase)); + if (key == null) return String.Empty; + using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(key)) + { + if (stream == null) + { + return String.Empty; + } + using (var reader = new StreamReader(stream, Encoding.UTF8, true, 128)) + { + return reader.ReadToEnd().MinifyJson(); + } + } + } + + public static string ProcessMetadata(Feat feat) + { + var manifestStreams = Assembly.GetExecutingAssembly().GetManifestResourceNames(); + var key = $"StarWars5e.Parser.Sources.metadata.Feats.{feat.Name.Replace(",", "").Replace(" ", "")}.json"; + key = manifestStreams.FirstOrDefault(s => s.Equals(key, StringComparison.OrdinalIgnoreCase)); + if (key == null) return String.Empty; + using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(key)) + { + if (stream == null) + { + return String.Empty; + } + using (var reader = new StreamReader(stream, Encoding.UTF8, true, 128)) + { + return reader.ReadToEnd().MinifyJson(); + } + } + } + } +} diff --git a/StarWars5e.Parser/Processors/PHB/PlayerHandbookClassProcessor.cs b/StarWars5e.Parser/Processors/PHB/PlayerHandbookClassProcessor.cs index 4ade6f2..e1071d5 100644 --- a/StarWars5e.Parser/Processors/PHB/PlayerHandbookClassProcessor.cs +++ b/StarWars5e.Parser/Processors/PHB/PlayerHandbookClassProcessor.cs @@ -408,7 +408,7 @@ public List ParseFeatures(List featureTextLines, string sourceN .Take(nextFeatureNameLineIndex - currentFeatureNameLineIndex).RemoveEmptyLines().ToList(); var name = featureLines[0].Split("# ")[1].Trim(); - + //var metadata = ReadMetadata($"features.{Enum.GetName(featureSource)}.{sourceName.Replace(" ", "")}.{name.Replace(" ", "")}"); var levels = GetFeatureLevels(featureLines.Skip(1).CleanListOfStrings().ToList()); @@ -425,7 +425,7 @@ public List ParseFeatures(List featureTextLines, string sourceN RowKey = $"{featureSource}-{sourceName}-{name}-{level}".Replace("/", string.Empty) .Replace(@"\", string.Empty) }; - + feature.Metadata = MetadataProcessor.ProcessMetadata(feature); features.Add(feature); } } diff --git a/StarWars5e.Parser/Processors/PHB/PlayerHandbookFeatProcessor.cs b/StarWars5e.Parser/Processors/PHB/PlayerHandbookFeatProcessor.cs index 2db73f6..07eec02 100644 --- a/StarWars5e.Parser/Processors/PHB/PlayerHandbookFeatProcessor.cs +++ b/StarWars5e.Parser/Processors/PHB/PlayerHandbookFeatProcessor.cs @@ -57,6 +57,8 @@ public Feat ParseFeat(List featLines, ContentType contentType) PartitionKey = contentType.ToString(), RowKey = name }; + + feat.Metadata = MetadataProcessor.ProcessMetadata(feat); var prerequisiteIndex = featLines.FindIndex(f => diff --git a/StarWars5e.Parser/Processors/PHB/PlayerHandbookLanguagesProcessor.cs b/StarWars5e.Parser/Processors/PHB/PlayerHandbookLanguagesProcessor.cs new file mode 100644 index 0000000..221c101 --- /dev/null +++ b/StarWars5e.Parser/Processors/PHB/PlayerHandbookLanguagesProcessor.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using StarWars5e.Models; +using StarWars5e.Models.Enums; +using StarWars5e.Models.Utils; + +namespace StarWars5e.Parser.Processors.PHB +{ + public class PlayerHandbookLanguagesProcessor : BaseProcessor + { + public override Task> FindBlocks(List lines) + { + var languages = new List(); + + var languagesStart = lines.FindIndex(x => x.StartsWith("##### Common Languages")); + var languagesEnd = lines.FindIndex(languagesStart + 1, x => x.StartsWith("#")); + + languages.AddRange(lines + .GetRange(languagesStart, languagesEnd - languagesStart) + .Where(l => l.StartsWith("|") && !l.StartsWith("||") && !l.StartsWith("|:")) + .Select(l => new CommonLanguage() + { + Name = l.Split("|")[1].Trim(), + RowKey = l.Split("|")[1].Trim(), + PartitionKey = "Core" + })); + + return Task.FromResult(languages); + } + } +} diff --git a/StarWars5e.Parser/Program.cs b/StarWars5e.Parser/Program.cs index 184c939..cef9146 100644 --- a/StarWars5e.Parser/Program.cs +++ b/StarWars5e.Parser/Program.cs @@ -62,9 +62,10 @@ public static async Task Main(string[] args) if (!string.IsNullOrWhiteSpace(config["SearchKey"])) { - var searchIndexClient = new SearchIndexClient(new Uri("https://sw5esearch.search.windows.net"), + var searchEndpoint = config["SearchEndpoint"] ?? "https://sw5esearch.search.windows.net"; + var searchIndexClient = new SearchIndexClient(new Uri(searchEndpoint), new AzureKeyCredential(config["SearchKey"])); - var searchIndexerClient = new SearchIndexerClient(new Uri("https://sw5esearch.search.windows.net"), + var searchIndexerClient = new SearchIndexerClient(new Uri(searchEndpoint), new AzureKeyCredential(config["SearchKey"])); serviceProvider.AddSingleton(searchIndexClient); diff --git a/StarWars5e.Parser/Sources/en/PHB/phb_04.txt b/StarWars5e.Parser/Sources/en/PHB/phb_04.txt index 7e222fb..908783e 100644 --- a/StarWars5e.Parser/Sources/en/PHB/phb_04.txt +++ b/StarWars5e.Parser/Sources/en/PHB/phb_04.txt @@ -109,6 +109,7 @@ A list of the most commonly spoken languages of *Star Wars* can be found below i | Tusken | | Twi'leki | | Zabraki | +| Kaminoan | diff --git a/StarWars5e.Parser/Sources/metadata/Feats/FightingMaster.json b/StarWars5e.Parser/Sources/metadata/Feats/FightingMaster.json new file mode 100644 index 0000000..8f6abc3 --- /dev/null +++ b/StarWars5e.Parser/Sources/metadata/Feats/FightingMaster.json @@ -0,0 +1,3 @@ +{ + "fightingMastery": 1 +} \ No newline at end of file diff --git a/StarWars5e.Parser/Sources/metadata/Feats/FightingStylist.json b/StarWars5e.Parser/Sources/metadata/Feats/FightingStylist.json new file mode 100644 index 0000000..c695dea --- /dev/null +++ b/StarWars5e.Parser/Sources/metadata/Feats/FightingStylist.json @@ -0,0 +1,3 @@ +{ + "fightingStyle": 1 +} \ No newline at end of file diff --git a/StarWars5e.Parser/Sources/metadata/Features/Archetype/TacticalSpecialist/BonusProficiencies.json b/StarWars5e.Parser/Sources/metadata/Features/Archetype/TacticalSpecialist/BonusProficiencies.json new file mode 100644 index 0000000..2baa1aa --- /dev/null +++ b/StarWars5e.Parser/Sources/metadata/Features/Archetype/TacticalSpecialist/BonusProficiencies.json @@ -0,0 +1,5 @@ +{ + "bonusProficiency": { + "type": "Tool" + } +} \ No newline at end of file diff --git a/StarWars5e.Parser/Sources/metadata/Features/Class/Fighter/FighterStrategies.json b/StarWars5e.Parser/Sources/metadata/Features/Class/Fighter/FighterStrategies.json new file mode 100644 index 0000000..8e66a98 --- /dev/null +++ b/StarWars5e.Parser/Sources/metadata/Features/Class/Fighter/FighterStrategies.json @@ -0,0 +1,3 @@ +{ + "fightingStrategy": 1 +} \ No newline at end of file diff --git a/StarWars5e.Parser/Sources/metadata/Features/Class/Fighter/FightingStyle.json b/StarWars5e.Parser/Sources/metadata/Features/Class/Fighter/FightingStyle.json new file mode 100644 index 0000000..c695dea --- /dev/null +++ b/StarWars5e.Parser/Sources/metadata/Features/Class/Fighter/FightingStyle.json @@ -0,0 +1,3 @@ +{ + "fightingStyle": 1 +} \ No newline at end of file diff --git a/StarWars5e.Parser/Sources/metadata/FightingStrategy/MasteryStrategist.json b/StarWars5e.Parser/Sources/metadata/FightingStrategy/MasteryStrategist.json new file mode 100644 index 0000000..8f6abc3 --- /dev/null +++ b/StarWars5e.Parser/Sources/metadata/FightingStrategy/MasteryStrategist.json @@ -0,0 +1,3 @@ +{ + "fightingMastery": 1 +} \ No newline at end of file diff --git a/StarWars5e.Parser/Sources/metadata/FightingStrategy/StyleStrategist.json b/StarWars5e.Parser/Sources/metadata/FightingStrategy/StyleStrategist.json new file mode 100644 index 0000000..f5f9bc1 --- /dev/null +++ b/StarWars5e.Parser/Sources/metadata/FightingStrategy/StyleStrategist.json @@ -0,0 +1,3 @@ +{ + "fightingStyle": 1 +} \ No newline at end of file diff --git a/StarWars5e.Parser/StarWars5e.Parser.csproj b/StarWars5e.Parser/StarWars5e.Parser.csproj index 55f4722..c61402b 100644 --- a/StarWars5e.Parser/StarWars5e.Parser.csproj +++ b/StarWars5e.Parser/StarWars5e.Parser.csproj @@ -492,6 +492,11 @@ + + + + +