From e0775baa71224fad84ebdc09baabe627fd3dbc58 Mon Sep 17 00:00:00 2001 From: Matt Lyons Date: Tue, 26 Sep 2023 12:19:18 -0500 Subject: [PATCH] Add C# project data support (#427) --- c-sharp-tests/c-sharp-tests.csproj | 14 +- .../JsonUtils/ProjectDataScopeConverter.cs | 63 +++++ c-sharp/JsonUtils/ProjectMetadataConverter.cs | 61 +++++ c-sharp/JsonUtils/VerseRefConverter.cs | 5 + c-sharp/NetworkObjects/DataProvider.cs | 171 ++++++++------ c-sharp/NetworkObjects/NetworkObject.cs | 59 +++-- c-sharp/NetworkObjects/TimeDataProvider.cs | 55 +++-- c-sharp/NetworkObjects/UsfmDataProvider.cs | 173 +++++++------- c-sharp/ParanextDataProvider.csproj | 8 +- c-sharp/ParanextDataProvider.sln.DotSettings | 6 +- c-sharp/Program.cs | 10 +- c-sharp/Projects/IProjectStreamManager.cs | 34 +++ c-sharp/Projects/LocalProjects.cs | 209 +++++++++++++++++ .../Projects/ParatextProjectDataProvider.cs | 83 +++++++ .../ParatextProjectDataProviderFactory.cs | 60 +++++ .../ParatextProjectStorageInterpreter.cs | 221 ++++++++++++++++++ c-sharp/Projects/ProjectDataProvider.cs | 120 ++++++++++ .../Projects/ProjectDataProviderFactory.cs | 76 ++++++ c-sharp/Projects/ProjectDataScope.cs | 32 +++ c-sharp/Projects/ProjectDetails.cs | 41 ++++ c-sharp/Projects/ProjectMetadata.cs | 66 ++++++ c-sharp/Projects/ProjectStorageInterpreter.cs | 120 ++++++++++ c-sharp/Projects/ProjectStorageType.cs | 12 + c-sharp/Projects/ProjectType.cs | 9 + .../RawDirectoryProjectStreamManager.cs | 116 +++++++++ cspell.json | 1 + lib/papi-dts/papi.d.ts | 17 +- src/main/main.ts | 29 +++ .../project-data-provider-factory.service.ts | 2 +- .../models/project-data-provider.model.ts | 18 +- src/shared/services/data-provider.service.ts | 10 +- 31 files changed, 1665 insertions(+), 236 deletions(-) create mode 100644 c-sharp/JsonUtils/ProjectDataScopeConverter.cs create mode 100644 c-sharp/JsonUtils/ProjectMetadataConverter.cs create mode 100644 c-sharp/Projects/IProjectStreamManager.cs create mode 100644 c-sharp/Projects/LocalProjects.cs create mode 100644 c-sharp/Projects/ParatextProjectDataProvider.cs create mode 100644 c-sharp/Projects/ParatextProjectDataProviderFactory.cs create mode 100644 c-sharp/Projects/ParatextProjectStorageInterpreter.cs create mode 100644 c-sharp/Projects/ProjectDataProvider.cs create mode 100644 c-sharp/Projects/ProjectDataProviderFactory.cs create mode 100644 c-sharp/Projects/ProjectDataScope.cs create mode 100644 c-sharp/Projects/ProjectDetails.cs create mode 100644 c-sharp/Projects/ProjectMetadata.cs create mode 100644 c-sharp/Projects/ProjectStorageInterpreter.cs create mode 100644 c-sharp/Projects/ProjectStorageType.cs create mode 100644 c-sharp/Projects/ProjectType.cs create mode 100644 c-sharp/Projects/RawDirectoryProjectStreamManager.cs diff --git a/c-sharp-tests/c-sharp-tests.csproj b/c-sharp-tests/c-sharp-tests.csproj index 1ab0e3438f..ae2fc2897d 100644 --- a/c-sharp-tests/c-sharp-tests.csproj +++ b/c-sharp-tests/c-sharp-tests.csproj @@ -12,11 +12,17 @@ - + - - - + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/c-sharp/JsonUtils/ProjectDataScopeConverter.cs b/c-sharp/JsonUtils/ProjectDataScopeConverter.cs new file mode 100644 index 0000000000..ce60d647ce --- /dev/null +++ b/c-sharp/JsonUtils/ProjectDataScopeConverter.cs @@ -0,0 +1,63 @@ +using Newtonsoft.Json.Linq; +using Paranext.DataProvider.Projects; + +namespace Paranext.DataProvider.JsonUtils; + +internal static class ProjectDataScopeConverter +{ + private const string PROJECT_ID = "projectId"; + private const string PROJECT_NAME = "projectName"; + private const string EXTENSION_NAME = "extensionName"; + private const string DATA_TYPE = "dataType"; + private const string DATA_QUALIFIER = "dataQualifier"; + + public static bool TryGetProjectDataScope( + string jsonString, + out ProjectDataScope? dataScope, + out string errorMessage + ) + { + try + { + JObject parsedArgs = JObject.Parse(jsonString); + dataScope = new ProjectDataScope() + { + ProjectID = Get(parsedArgs, PROJECT_ID), + ProjectName = Get(parsedArgs, PROJECT_NAME), + ExtensionName = Get(parsedArgs, EXTENSION_NAME), + DataType = Get(parsedArgs, DATA_TYPE), + DataQualifier = Get(parsedArgs, DATA_QUALIFIER) + }; + if ( + (dataScope.ProjectID == null) + && (dataScope.ProjectName == null) + && (dataScope.ExtensionName == null) + && (dataScope.DataType == null) + && (dataScope.DataQualifier == null) + ) + { + throw new Exception("Data scope cannot be empty"); + } + } + catch (Exception ex) + { + dataScope = null; + errorMessage = $"Failed to parse \"{jsonString}\" into ProjectDataScope: {ex}"; + return false; + } + + errorMessage = ""; + return true; + } + + private static string? Get(JObject jObject, string propertyName) + { + if ( + !jObject.TryGetValue(propertyName, out var property) + || (property.Value() == null) + ) + return null; + + return property.Value()!; + } +} diff --git a/c-sharp/JsonUtils/ProjectMetadataConverter.cs b/c-sharp/JsonUtils/ProjectMetadataConverter.cs new file mode 100644 index 0000000000..3bebb2d518 --- /dev/null +++ b/c-sharp/JsonUtils/ProjectMetadataConverter.cs @@ -0,0 +1,61 @@ +using Newtonsoft.Json.Linq; +using Paranext.DataProvider.Projects; + +namespace Paranext.DataProvider.JsonUtils +{ + internal static class ProjectMetadataConverter + { + private const string ID = "id"; + private const string NAME = "name"; + private const string STORAGE_TYPE = "storageType"; + private const string PROJECT_TYPE = "projectType"; + + public static bool TryGetMetadata( + string jsonString, + out ProjectMetadata? projectMetadata, + out string errorMessage + ) + { + try + { + JObject parsedArgs = JObject.Parse(jsonString); + string id = Get(parsedArgs, ID); + string name = Get(parsedArgs, NAME); + string projectStorageType = Get(parsedArgs, STORAGE_TYPE); + string projectType = Get(parsedArgs, PROJECT_TYPE); + projectMetadata = new ProjectMetadata(id, name, projectStorageType, projectType); + } + catch (Exception ex) + { + projectMetadata = null; + errorMessage = ex.ToString(); + return false; + } + + errorMessage = ""; + return true; + } + + private static string Get(JObject jObject, string propertyName) + { + if ( + !jObject.TryGetValue(propertyName, out var property) + || (property.Value() == null) + ) + throw new ArgumentException($"Missing \"{propertyName}\" property in JSON"); + + return property.Value()!; + } + + public static string ToJsonString(ProjectMetadata projectMetadata) + { + return new JObject + { + [ID] = projectMetadata.ID, + [NAME] = projectMetadata.Name, + [STORAGE_TYPE] = projectMetadata.ProjectStorageType, + [PROJECT_TYPE] = projectMetadata.ProjectType + }.ToString(); + } + } +} diff --git a/c-sharp/JsonUtils/VerseRefConverter.cs b/c-sharp/JsonUtils/VerseRefConverter.cs index c6f85bf20b..929b0eaba8 100644 --- a/c-sharp/JsonUtils/VerseRefConverter.cs +++ b/c-sharp/JsonUtils/VerseRefConverter.cs @@ -5,6 +5,10 @@ namespace Paranext.DataProvider.JsonUtils { internal class VerseRefConverter { + /// + /// Attempts to convert a string containing JSON to a VerseRef object + /// + /// true if the conversion was successful, false otherwise public static bool TryCreateVerseRef( string jsonString, out VerseRef verseRef, @@ -83,6 +87,7 @@ out string errorMessage versification ); } + return true; } } diff --git a/c-sharp/NetworkObjects/DataProvider.cs b/c-sharp/NetworkObjects/DataProvider.cs index dd62b8d7e3..f9a3ccfa08 100644 --- a/c-sharp/NetworkObjects/DataProvider.cs +++ b/c-sharp/NetworkObjects/DataProvider.cs @@ -1,3 +1,4 @@ +using Newtonsoft.Json; using Paranext.DataProvider.MessageHandlers; using Paranext.DataProvider.Messages; using Paranext.DataProvider.MessageTransports; @@ -6,100 +7,120 @@ using System.Text.Json; using System.Text.Json.Nodes; -namespace Paranext.DataProvider.NetworkObjects +namespace Paranext.DataProvider.NetworkObjects; + +internal abstract class DataProvider : NetworkObject { - internal abstract class DataProvider : NetworkObject + // This is an internal class because nothing else should be instantiating it directly + private class MessageEventDataUpdated : MessageEventGeneric { - // This is an internal class because nothing else should be instantiating it directly - private class MessageEventDataUpdated : MessageEventGeneric - { - // A parameterless constructor is required for serialization to work - // ReSharper disable once UnusedMember.Local - public MessageEventDataUpdated() - : base(Enum.Null) { } + // A parameterless constructor is required for serialization to work + // ReSharper disable once UnusedMember.Local + public MessageEventDataUpdated() + : base(Enum.Null) { } - public MessageEventDataUpdated(Enum eventType, string dataScope) - : base(eventType, dataScope) { } - } + public MessageEventDataUpdated(Enum eventType, string dataScope) + : base(eventType, dataScope) { } + } - private readonly Enum _eventType; - private readonly ConcurrentDictionary< - string, - MessageEventDataUpdated - > _updateEventsByScope = new(); + private readonly Enum _eventType; + private readonly ConcurrentDictionary _updateEventsByScope = + new(); - protected DataProvider(string name, PapiClient papiClient) - : base(papiClient) - { - // "-data" is the suffix used by PAPI for data provider names - DataProviderName = name + "-data"; + protected DataProvider(string name, PapiClient papiClient) + : base(papiClient) + { + // "-data" is the suffix used by PAPI for data provider names + DataProviderName = name + "-data"; - // "onDidUpdate" is the event name used by PAPI for data providers to notify consumers of updates - _eventType = new Enum($"{DataProviderName}:onDidUpdate"); - } + // "onDidUpdate" is the event name used by PAPI for data providers to notify consumers of updates + _eventType = new Enum($"{DataProviderName}:onDidUpdate"); + } + + public string DataProviderName { get; } - protected string DataProviderName { get; } + /// + /// Register this data provider on the network so that other services can use it + /// + public async Task RegisterDataProvider() + { + await RegisterNetworkObject(DataProviderName, FunctionHandler); + await StartDataProvider(); + } - /// - /// Register this data provider on the network so that other services can use it - /// - public async Task RegisterDataProvider() + // An array of strings serialized as JSON will be sent here. + // The first item in the array is the name of the function to call. + // All remaining items are arguments to pass to the function. + // Data providers must provide "get" and "set" functions. + private ResponseToRequest FunctionHandler(dynamic? request) + { + string functionName; + JsonArray jsonArray; + try { - await RegisterNetworkObject(DataProviderName, FunctionHandler); - await StartDataProvider(); + jsonArray = ((JsonElement)request!).Deserialize()!.AsArray(); + if (jsonArray.Count == 0) + return ResponseToRequest.Failed( + $"No function name provided when calling data provider {DataProviderName}" + ); + functionName = (string)jsonArray[0]!; + jsonArray.RemoveAt(0); } + catch (Exception e) + { + Console.Error.WriteLine(e.ToString()); + return ResponseToRequest.Failed("Invalid function call data"); + } + + return HandleRequest(functionName, jsonArray); + } - // An array of strings serialized as JSON will be sent here. - // The first item in the array is the name of the function to call. - // All remaining items are arguments to pass to the function. - // Data providers must provide "get" and "set" functions. - private ResponseToRequest FunctionHandler(dynamic? request) + /// + /// Notify all processes on the network that this data provider has new data + /// + /// Indicator of what data changed in the provider + protected void SendDataUpdateEvent(dynamic? dataScope) + { + string scopeString; + + if ((dataScope is string s) && !string.IsNullOrWhiteSpace(s)) + { + // If we are returning "*", just pass it as a string. Otherwise we have to provide a JSON list of strings. + // Presumably this will change as part of https://github.com/paranext/paranext-core/issues/443 + scopeString = (s == "*") ? s : JsonConvert.SerializeObject(new List { s }); + } + else if (dataScope != null) { - string functionName; - JsonArray jsonArray; try { - jsonArray = ((JsonElement)request!).Deserialize()!.AsArray(); - if (jsonArray.Count == 0) - return ResponseToRequest.Failed( - $"No function name provided when calling data provider {DataProviderName}" - ); - functionName = (string)jsonArray[0]!; - jsonArray.RemoveAt(0); + scopeString = JsonConvert.SerializeObject(dataScope); } - catch (Exception e) + catch (Exception ex) { - Console.Error.WriteLine(e.ToString()); - return ResponseToRequest.Failed("Invalid function call data"); + Console.WriteLine($"Unable to send data update event: {ex}"); + return; } - - return HandleRequest(functionName, jsonArray); } + else + return; - /// - /// Notify all processes on the network that this data provider has new data - /// - /// Indicator of what data changed in the provider - protected void SendDataUpdateEvent(string dataScope) - { - var dataUpdateEventMessage = _updateEventsByScope.GetOrAdd( - dataScope, - (scope) => new MessageEventDataUpdated(_eventType, scope) - ); - PapiClient.SendEvent(dataUpdateEventMessage); - } + var dataUpdateEventMessage = _updateEventsByScope.GetOrAdd( + scopeString, + (scope) => new MessageEventDataUpdated(_eventType, scope) + ); + PapiClient.SendEvent(dataUpdateEventMessage); + } - /// - /// Once a data provider has started, it should send out update events whenever its data changes. - /// - protected abstract Task StartDataProvider(); + /// + /// Once a data provider has started, it should send out update events whenever its data changes. + /// + protected abstract Task StartDataProvider(); - /// - /// Handle a request from a service using this data provider - /// - /// This would typically be "getXYZ" or "setXYZ", where "XYZ" is a type of data handled by this provider - /// Optional arguments provided by the requester for the function indicated - /// ResponseToRequest value that either contains a response for the function or an error message - protected abstract ResponseToRequest HandleRequest(string functionName, JsonArray args); - } + /// + /// Handle a request from a service using this data provider + /// + /// This would typically be "getXYZ" or "setXYZ", where "XYZ" is a type of data handled by this provider + /// Optional arguments provided by the requester for the function indicated + /// ResponseToRequest value that either contains a response for the function or an error message + protected abstract ResponseToRequest HandleRequest(string functionName, JsonArray args); } diff --git a/c-sharp/NetworkObjects/NetworkObject.cs b/c-sharp/NetworkObjects/NetworkObject.cs index 7b26847953..390d0789ce 100644 --- a/c-sharp/NetworkObjects/NetworkObject.cs +++ b/c-sharp/NetworkObjects/NetworkObject.cs @@ -3,42 +3,41 @@ using Paranext.DataProvider.MessageTransports; using PtxUtils; -namespace Paranext.DataProvider.NetworkObjects +namespace Paranext.DataProvider.NetworkObjects; + +internal abstract class NetworkObject { - internal abstract class NetworkObject + protected NetworkObject(PapiClient papiClient) { - protected NetworkObject(PapiClient papiClient) - { - PapiClient = papiClient; - } + PapiClient = papiClient; + } - protected PapiClient PapiClient { get; } + protected PapiClient PapiClient { get; } - /// - /// Notify PAPI services we have a new network object they can use - /// - /// Services access this network object using this name - /// Function that will handle calls from services to this network object - /// Throws if the network object could not be registered properly - protected async Task RegisterNetworkObject( - string networkObjectName, - Func requestHandler - ) - { - // PAPI requires network objects to expose "get" and "function" requests - var getReqType = new Enum($"object:{networkObjectName}.get"); - var functionReqType = new Enum($"object:{networkObjectName}.function"); + /// + /// Notify PAPI services we have a new network object they can use + /// + /// Services access this network object using this name + /// Function that will handle calls from services to this network object + /// Throws if the network object could not be registered properly + protected async Task RegisterNetworkObject( + string networkObjectName, + Func requestHandler + ) + { + // PAPI requires network objects to expose "get" and "function" requests + var getReqType = new Enum($"object:{networkObjectName}.get"); + var functionReqType = new Enum($"object:{networkObjectName}.function"); - if (!await PapiClient.RegisterRequestHandler(getReqType, HandleGet)) - throw new Exception($"Could not register GET for {networkObjectName}"); + if (!await PapiClient.RegisterRequestHandler(getReqType, HandleGet)) + throw new Exception($"Could not register GET for {networkObjectName}"); - if (!await PapiClient.RegisterRequestHandler(functionReqType, requestHandler)) - throw new Exception($"Could not register FUNCTION for {networkObjectName}"); - } + if (!await PapiClient.RegisterRequestHandler(functionReqType, requestHandler)) + throw new Exception($"Could not register FUNCTION for {networkObjectName}"); + } - private static ResponseToRequest HandleGet(dynamic getRequest) - { - return ResponseToRequest.Succeeded(); - } + private static ResponseToRequest HandleGet(dynamic getRequest) + { + return ResponseToRequest.Succeeded(); } } diff --git a/c-sharp/NetworkObjects/TimeDataProvider.cs b/c-sharp/NetworkObjects/TimeDataProvider.cs index 0d1c187241..074dc66199 100644 --- a/c-sharp/NetworkObjects/TimeDataProvider.cs +++ b/c-sharp/NetworkObjects/TimeDataProvider.cs @@ -3,39 +3,38 @@ using Paranext.DataProvider.MessageTransports; using SIL.Extensions; -namespace Paranext.DataProvider.NetworkObjects +namespace Paranext.DataProvider.NetworkObjects; + +/// +/// This is a sample data provider for demonstration purposes +/// +internal class TimeDataProvider : DataProvider { - /// - /// This is a sample data provider for demonstration purposes - /// - internal class TimeDataProvider : DataProvider - { - // Fire an event that says our "time data" updated once per second - private readonly System.Timers.Timer _timer = new(TimeSpan.FromSeconds(1)); + // Fire an event that says our "time data" updated once per second + private readonly System.Timers.Timer _timer = new(TimeSpan.FromSeconds(1)); - public TimeDataProvider(PapiClient papiClient) - : base("current-time", papiClient) { } + public TimeDataProvider(PapiClient papiClient) + : base("current-time", papiClient) { } - protected override Task StartDataProvider() + protected override Task StartDataProvider() + { + _timer.Elapsed += (_, _) => { - _timer.Elapsed += (_, _) => - { - SendDataUpdateEvent("*"); - }; - _timer.AutoReset = true; - _timer.Enabled = true; - return Task.CompletedTask; - } + SendDataUpdateEvent("*"); + }; + _timer.AutoReset = true; + _timer.Enabled = true; + return Task.CompletedTask; + } - protected override ResponseToRequest HandleRequest(string functionName, JsonArray args) + protected override ResponseToRequest HandleRequest(string functionName, JsonArray args) + { + return functionName switch { - return functionName switch - { - "getTime" - => ResponseToRequest.Succeeded(DateTime.Now.ToISO8601TimeFormatWithUTCString()), - "setTime" => ResponseToRequest.Failed("Cannot set the time"), - _ => ResponseToRequest.Failed($"Unexpected function: {functionName}") - }; - } + "getTime" + => ResponseToRequest.Succeeded(DateTime.Now.ToISO8601TimeFormatWithUTCString()), + "setTime" => ResponseToRequest.Failed("Cannot set the time"), + _ => ResponseToRequest.Failed($"Unexpected function: {functionName}") + }; } } diff --git a/c-sharp/NetworkObjects/UsfmDataProvider.cs b/c-sharp/NetworkObjects/UsfmDataProvider.cs index 7a850e62bf..865ec0f612 100644 --- a/c-sharp/NetworkObjects/UsfmDataProvider.cs +++ b/c-sharp/NetworkObjects/UsfmDataProvider.cs @@ -8,114 +8,113 @@ using Paratext.Data; using SIL.Scripture; -namespace Paranext.DataProvider.NetworkObjects +namespace Paranext.DataProvider.NetworkObjects; + +internal class UsfmDataProvider : DataProvider { - internal class UsfmDataProvider : DataProvider + private readonly string _collectionName; + private ScrText? _scrText; + + public UsfmDataProvider(PapiClient papiClient, string dataFolderPath, string collectionName) + : base("usfm", papiClient) { - private readonly string _collectionName; - private ScrText? _scrText; + _collectionName = collectionName; + ParatextGlobals.Initialize(dataFolderPath); + } - public UsfmDataProvider(PapiClient papiClient, string dataFolderPath, string collectionName) - : base("usfm", papiClient) - { - _collectionName = collectionName; - ParatextGlobals.Initialize(dataFolderPath); - } + protected override Task StartDataProvider() + { + _scrText = ScrTextCollection.Find(_collectionName); + return Task.CompletedTask; + } - protected override Task StartDataProvider() + protected override ResponseToRequest HandleRequest(string functionName, JsonArray args) + { + if (_scrText == null) { - _scrText = ScrTextCollection.Find(_collectionName); - return Task.CompletedTask; + Console.Error.WriteLine("StartDataProvider must be called first"); + return ResponseToRequest.Failed("Data provider must be started first"); } - protected override ResponseToRequest HandleRequest(string functionName, JsonArray args) + try { - if (_scrText == null) - { - Console.Error.WriteLine("StartDataProvider must be called first"); - return ResponseToRequest.Failed("Data provider must be started first"); - } - - try - { - return functionName switch - { - "getBookNames" => GetBookNames(), - "getChapter" => GetChapter(args[0]!.ToJsonString()), - "getChapterUsx" => GetChapterUsx(args[0]!.ToJsonString()), - "getVerse" => GetVerse(args[0]!.ToJsonString()), - _ => ResponseToRequest.Failed($"Unexpected function: {functionName}") - }; - } - catch (Exception e) + return functionName switch { - Console.Error.WriteLine(e.ToString()); - return ResponseToRequest.Failed(e.Message); - } + "getBookNames" => GetBookNames(), + "getChapter" => GetChapter(args[0]!.ToJsonString()), + "getChapterUsx" => GetChapterUsx(args[0]!.ToJsonString()), + "getVerse" => GetVerse(args[0]!.ToJsonString()), + _ => ResponseToRequest.Failed($"Unexpected function: {functionName}") + }; } - - private static ResponseToRequest GetBookNames() + catch (Exception e) { - return ResponseToRequest.Succeeded(JsonSerializer.Serialize(Canon.AllBookIds)); + Console.Error.WriteLine(e.ToString()); + return ResponseToRequest.Failed(e.Message); } + } - private ResponseToRequest GetChapter(string args) - { - return VerseRefConverter.TryCreateVerseRef(args, out var verseRef, out string errorMsg) - ? ResponseToRequest.Succeeded(_scrText!.GetText(verseRef, true, true)) - : ResponseToRequest.Failed(errorMsg); - } + private static ResponseToRequest GetBookNames() + { + return ResponseToRequest.Succeeded(JsonSerializer.Serialize(Canon.AllBookIds)); + } - private ResponseToRequest GetChapterUsx(string args) - { - return VerseRefConverter.TryCreateVerseRef(args, out var verseRef, out string errorMsg) - ? ResponseToRequest.Succeeded(GetUsx(verseRef)) - : ResponseToRequest.Failed(errorMsg); - } + private ResponseToRequest GetChapter(string args) + { + return VerseRefConverter.TryCreateVerseRef(args, out var verseRef, out string errorMsg) + ? ResponseToRequest.Succeeded(_scrText!.GetText(verseRef, true, true)) + : ResponseToRequest.Failed(errorMsg); + } - private ResponseToRequest GetVerse(string args) - { - return VerseRefConverter.TryCreateVerseRef(args, out var verseRef, out string errorMsg) - ? ResponseToRequest.Succeeded(_scrText!.GetVerseText(verseRef)) - : ResponseToRequest.Failed(errorMsg); - } + private ResponseToRequest GetChapterUsx(string args) + { + return VerseRefConverter.TryCreateVerseRef(args, out var verseRef, out string errorMsg) + ? ResponseToRequest.Succeeded(GetUsx(verseRef)) + : ResponseToRequest.Failed(errorMsg); + } - public string GetUsx(VerseRef vref) - { - XmlDocument usx = GetUsxForChapter(vref.BookNum, vref.ChapterNum); - string contents = usx.OuterXml ?? string.Empty; - return contents; - } + private ResponseToRequest GetVerse(string args) + { + return VerseRefConverter.TryCreateVerseRef(args, out var verseRef, out string errorMsg) + ? ResponseToRequest.Succeeded(_scrText!.GetVerseText(verseRef)) + : ResponseToRequest.Failed(errorMsg); + } - private XmlDocument GetUsxForChapter(int bookNum, int chapterNum) - { - return ConvertUsfmToUsx(GetUsfmForChapter(bookNum, chapterNum), bookNum); - } + public string GetUsx(VerseRef vref) + { + XmlDocument usx = GetUsxForChapter(vref.BookNum, vref.ChapterNum); + string contents = usx.OuterXml ?? string.Empty; + return contents; + } - /// - /// Converts usfm to usx, but does not annotate - /// - private XmlDocument ConvertUsfmToUsx(string usfm, int bookNum) - { - ScrStylesheet scrStylesheet = _scrText!.ScrStylesheet(bookNum); - // Tokenize usfm - List tokens = UsfmToken.Tokenize(scrStylesheet, usfm ?? string.Empty, true); + private XmlDocument GetUsxForChapter(int bookNum, int chapterNum) + { + return ConvertUsfmToUsx(GetUsfmForChapter(bookNum, chapterNum), bookNum); + } - XmlDocument doc = new XmlDocument(); - using (XmlWriter xmlw = doc.CreateNavigator()!.AppendChild()) - { - // Convert to XML - UsfmToUsx.ConvertToXmlWriter(scrStylesheet, tokens, xmlw, false); - xmlw.Flush(); - } - return doc; - } + /// + /// Converts usfm to usx, but does not annotate + /// + private XmlDocument ConvertUsfmToUsx(string usfm, int bookNum) + { + ScrStylesheet scrStylesheet = _scrText!.ScrStylesheet(bookNum); + // Tokenize usfm + List tokens = UsfmToken.Tokenize(scrStylesheet, usfm ?? string.Empty, true); - private string GetUsfmForChapter(int bookNum, int chapterNum) + XmlDocument doc = new XmlDocument(); + using (XmlWriter xmlw = doc.CreateNavigator()!.AppendChild()) { - VerseRef vref = new(bookNum, chapterNum, 0, _scrText!.Settings.Versification); - ScrText projectToUse = _scrText!.GetJoinedText(bookNum); - return projectToUse.GetText(vref, true, true); + // Convert to XML + UsfmToUsx.ConvertToXmlWriter(scrStylesheet, tokens, xmlw, false); + xmlw.Flush(); } + return doc; + } + + private string GetUsfmForChapter(int bookNum, int chapterNum) + { + VerseRef vref = new(bookNum, chapterNum, 0, _scrText!.Settings.Versification); + ScrText projectToUse = _scrText!.GetJoinedText(bookNum); + return projectToUse.GetText(vref, true, true); } } diff --git a/c-sharp/ParanextDataProvider.csproj b/c-sharp/ParanextDataProvider.csproj index 9cc425bcd7..41855d7042 100644 --- a/c-sharp/ParanextDataProvider.csproj +++ b/c-sharp/ParanextDataProvider.csproj @@ -23,13 +23,13 @@ - - - + + + - +