From 77ce31128c0e61fcc8487a759f80485ab27281a0 Mon Sep 17 00:00:00 2001 From: Ben Date: Sun, 18 Feb 2024 21:37:06 +0800 Subject: [PATCH] Fix #3833: introduce token replace class. --- .../Controllers/ModuleDefinitionController.cs | 90 ++++++---- Oqtane.Server/Controllers/ThemeController.cs | 88 ++++++---- .../OqtaneServiceCollectionExtensions.cs | 3 + .../Interfaces/ITokenReplace.cs | 27 +++ Oqtane.Server/Infrastructure/TokenReplace.cs | 158 ++++++++++++++++++ Oqtane.Shared/Interfaces/ITokenSource.cs | 13 ++ 6 files changed, 319 insertions(+), 60 deletions(-) create mode 100644 Oqtane.Server/Infrastructure/Interfaces/ITokenReplace.cs create mode 100644 Oqtane.Server/Infrastructure/TokenReplace.cs create mode 100644 Oqtane.Shared/Interfaces/ITokenSource.cs diff --git a/Oqtane.Server/Controllers/ModuleDefinitionController.cs b/Oqtane.Server/Controllers/ModuleDefinitionController.cs index 4fbea7eb..39ae9f48 100644 --- a/Oqtane.Server/Controllers/ModuleDefinitionController.cs +++ b/Oqtane.Server/Controllers/ModuleDefinitionController.cs @@ -16,6 +16,7 @@ using Microsoft.Extensions.DependencyInjection; using System.Text.Json; using System.Net; using Oqtane.Modules; +using Oqtane.Infrastructure.Interfaces; namespace Oqtane.Controllers { @@ -319,52 +320,33 @@ namespace Oqtane.Controllers private void ProcessTemplatesRecursively(DirectoryInfo current, string rootPath, string rootFolder, string templatePath, ModuleDefinition moduleDefinition) { + var tokenReplace = InitializeTokenReplace(rootPath, rootFolder, moduleDefinition); + // process folder - string folderPath = Utilities.PathCombine(rootPath, current.FullName.Replace(templatePath, "")); - folderPath = folderPath.Replace("[Owner]", moduleDefinition.Owner); - folderPath = folderPath.Replace("[Module]", moduleDefinition.Name); + var folderPath = Utilities.PathCombine(rootPath, current.FullName.Replace(templatePath, "")); + folderPath = tokenReplace.ReplaceTokens(folderPath); if (!Directory.Exists(folderPath)) { Directory.CreateDirectory(folderPath); } - FileInfo[] files = current.GetFiles("*.*"); + tokenReplace.AddSource("Folder", folderPath); + var files = current.GetFiles("*.*"); if (files != null) { foreach (FileInfo file in files) { // process file - string filePath = Path.Combine(folderPath, file.Name); - filePath = filePath.Replace("[Owner]", moduleDefinition.Owner); - filePath = filePath.Replace("[Module]", moduleDefinition.Name); + var filePath = Path.Combine(folderPath, file.Name); + filePath = tokenReplace.ReplaceTokens(filePath); + tokenReplace.AddSource("File", Path.GetFileName(filePath)); - string text = System.IO.File.ReadAllText(file.FullName); - text = text.Replace("[Owner]", moduleDefinition.Owner); - text = text.Replace("[Module]", moduleDefinition.Name); - text = text.Replace("[Description]", moduleDefinition.Description); - text = text.Replace("[RootPath]", rootPath); - text = text.Replace("[RootFolder]", rootFolder); - text = text.Replace("[ServerManagerType]", moduleDefinition.ServerManagerType); - text = text.Replace("[Folder]", folderPath); - text = text.Replace("[File]", Path.GetFileName(filePath)); - if (moduleDefinition.Version == "local") - { - text = text.Replace("[FrameworkVersion]", Constants.Version); - text = text.Replace("[ClientReference]", $"..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net8.0\\Oqtane.Client.dll"); - text = text.Replace("[ServerReference]", $"..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net8.0\\Oqtane.Server.dll"); - text = text.Replace("[SharedReference]", $"..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net8.0\\Oqtane.Shared.dll"); - } - else - { - text = text.Replace("[FrameworkVersion]", moduleDefinition.Version); - text = text.Replace("[ClientReference]", ""); - text = text.Replace("[ServerReference]", ""); - text = text.Replace("[SharedReference]", ""); - } + var text = System.IO.File.ReadAllText(file.FullName); + text = tokenReplace.ReplaceTokens(text); System.IO.File.WriteAllText(filePath, text); } - DirectoryInfo[] folders = current.GetDirectories(); + var folders = current.GetDirectories(); foreach (DirectoryInfo folder in folders.Reverse()) { @@ -372,5 +354,51 @@ namespace Oqtane.Controllers } } } + + private ITokenReplace InitializeTokenReplace(string rootPath, string rootFolder, ModuleDefinition moduleDefinition) + { + var tokenReplace = _serviceProvider.GetService(); + tokenReplace.AddSource(() => + { + return new Dictionary + { + { "RootPath", rootPath }, + { "RootFolder", rootFolder }, + { "Owner", moduleDefinition.Owner }, + { "Module", moduleDefinition.Name }, + { "Description", moduleDefinition.Description }, + { "ServerManagerType", moduleDefinition.ServerManagerType } + }; + }); + + if (moduleDefinition.Version == "local") + { + tokenReplace.AddSource(() => + { + return new Dictionary() + { + { "FrameworkVersion", Constants.Version }, + { "ClientReference", $"..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net8.0\\Oqtane.Client.dll" }, + { "ServerReference", $"..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net8.0\\Oqtane.Server.dll" }, + { "SharedReference", $"..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net8.0\\Oqtane.Shared.dll" }, + }; + }); + } + else + { + tokenReplace.AddSource(() => + { + return new Dictionary() + { + { "FrameworkVersion", moduleDefinition.Version }, + { "ClientReference", $"" }, + { "ServerReference", $"" }, + { "SharedReference", $"" }, + }; + }); + } + + return tokenReplace; + } } } diff --git a/Oqtane.Server/Controllers/ThemeController.cs b/Oqtane.Server/Controllers/ThemeController.cs index 40067fa3..f871d1d7 100644 --- a/Oqtane.Server/Controllers/ThemeController.cs +++ b/Oqtane.Server/Controllers/ThemeController.cs @@ -14,6 +14,8 @@ using System.Text.Json; using System.Net; using System.Reflection.Metadata; using System; +using Microsoft.Extensions.DependencyInjection; +using Oqtane.Infrastructure.Interfaces; // ReSharper disable StringIndexOfIsCultureSpecific.1 @@ -29,8 +31,9 @@ namespace Oqtane.Controllers private readonly ISyncManager _syncManager; private readonly ILogManager _logger; private readonly Alias _alias; + private readonly IServiceProvider _serviceProvider; - public ThemeController(IThemeRepository themes, IInstallationManager installationManager, IWebHostEnvironment environment, ITenantManager tenantManager, ISyncManager syncManager, ILogManager logger) + public ThemeController(IThemeRepository themes, IInstallationManager installationManager, IWebHostEnvironment environment, ITenantManager tenantManager, ISyncManager syncManager, ILogManager logger, IServiceProvider serviceProvider) { _themes = themes; _installationManager = installationManager; @@ -39,6 +42,7 @@ namespace Oqtane.Controllers _syncManager = syncManager; _logger = logger; _alias = tenantManager.GetAlias(); + _serviceProvider = serviceProvider; } // GET: api/ @@ -208,54 +212,80 @@ namespace Oqtane.Controllers private void ProcessTemplatesRecursively(DirectoryInfo current, string rootPath, string rootFolder, string templatePath, Theme theme) { + var tokenReplace = InitializeTokenReplace(rootPath, rootFolder, theme); + // process folder - string folderPath = Utilities.PathCombine(rootPath, current.FullName.Replace(templatePath, "")); - folderPath = folderPath.Replace("[Owner]", theme.Owner); - folderPath = folderPath.Replace("[Theme]", theme.Name); + var folderPath = Utilities.PathCombine(rootPath, current.FullName.Replace(templatePath, "")); + folderPath = tokenReplace.ReplaceTokens(folderPath); if (!Directory.Exists(folderPath)) { Directory.CreateDirectory(folderPath); } - FileInfo[] files = current.GetFiles("*.*"); + tokenReplace.AddSource("Folder", folderPath); + var files = current.GetFiles("*.*"); if (files != null) { foreach (FileInfo file in files) { // process file - string filePath = Path.Combine(folderPath, file.Name); - filePath = filePath.Replace("[Owner]", theme.Owner); - filePath = filePath.Replace("[Theme]", theme.Name); + var filePath = Path.Combine(folderPath, file.Name); + filePath = tokenReplace.ReplaceTokens(filePath); + tokenReplace.AddSource("File", Path.GetFileName(filePath)); - string text = System.IO.File.ReadAllText(file.FullName); - text = text.Replace("[Owner]", theme.Owner); - text = text.Replace("[Theme]", theme.Name); - text = text.Replace("[RootPath]", rootPath); - text = text.Replace("[RootFolder]", rootFolder); - text = text.Replace("[Folder]", folderPath); - text = text.Replace("[File]", Path.GetFileName(filePath)); - if (theme.Version == "local") - { - text = text.Replace("[FrameworkVersion]", Constants.Version); - text = text.Replace("[ClientReference]", $"..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net8.0\\Oqtane.Client.dll"); - text = text.Replace("[SharedReference]", $"..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net8.0\\Oqtane.Shared.dll"); - } - else - { - text = text.Replace("[FrameworkVersion]", theme.Version); - text = text.Replace("[ClientReference]", ""); - text = text.Replace("[SharedReference]", ""); - } + var text = System.IO.File.ReadAllText(file.FullName); + text = tokenReplace.ReplaceTokens(text); System.IO.File.WriteAllText(filePath, text); } - DirectoryInfo[] folders = current.GetDirectories(); - + var folders = current.GetDirectories(); foreach (DirectoryInfo folder in folders.Reverse()) { ProcessTemplatesRecursively(folder, rootPath, rootFolder, templatePath, theme); } } } + + private ITokenReplace InitializeTokenReplace(string rootPath, string rootFolder, Theme theme) + { + var tokenReplace = _serviceProvider.GetService(); + tokenReplace.AddSource(() => + { + return new Dictionary + { + { "RootPath", rootPath }, + { "RootFolder", rootFolder }, + { "Owner", theme.Owner }, + { "Theme", theme.Name } + }; + }); + + if (theme.Version == "local") + { + tokenReplace.AddSource(() => + { + return new Dictionary() + { + { "FrameworkVersion", Constants.Version }, + { "ClientReference", $"..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net8.0\\Oqtane.Client.dll" }, + { "SharedReference", $"..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net8.0\\Oqtane.Shared.dll" }, + }; + }); + } + else + { + tokenReplace.AddSource(() => + { + return new Dictionary() + { + { "FrameworkVersion", theme.Version }, + { "ClientReference", $"" }, + { "SharedReference", $"" }, + }; + }); + } + + return tokenReplace; + } } } diff --git a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs index dc742cbf..a3324e49 100644 --- a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs +++ b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs @@ -19,6 +19,7 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.OpenApi.Models; using Oqtane.Infrastructure; +using Oqtane.Infrastructure.Interfaces; using Oqtane.Managers; using Oqtane.Models; using Oqtane.Modules; @@ -150,6 +151,8 @@ namespace Microsoft.Extensions.DependencyInjection // obsolete - replaced by ITenantManager services.AddTransient(); + services.AddTransient(); + return services; } diff --git a/Oqtane.Server/Infrastructure/Interfaces/ITokenReplace.cs b/Oqtane.Server/Infrastructure/Interfaces/ITokenReplace.cs new file mode 100644 index 00000000..ff934a17 --- /dev/null +++ b/Oqtane.Server/Infrastructure/Interfaces/ITokenReplace.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using Oqtane.Interfaces; + +namespace Oqtane.Infrastructure.Interfaces +{ + public interface ITokenReplace + { + void AddSource(ITokenSource source); + + void AddSource(Func> sourceFunc); + + void AddSource(IDictionary source); + + void AddSource(string key, object value); + + void AddSource(string name, ITokenSource source); + + void AddSource(string name, Func> sourceFunc); + + void AddSource(string name, IDictionary source); + + void AddSource(string name, string key, object value); + + string ReplaceTokens(string source); + } +} diff --git a/Oqtane.Server/Infrastructure/TokenReplace.cs b/Oqtane.Server/Infrastructure/TokenReplace.cs new file mode 100644 index 00000000..3c7e8a8c --- /dev/null +++ b/Oqtane.Server/Infrastructure/TokenReplace.cs @@ -0,0 +1,158 @@ +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using System.Text; +using Oqtane.Infrastructure.Interfaces; +using Oqtane.Interfaces; +using Oqtane.Models; + +namespace Oqtane.Infrastructure +{ + public class TokenReplace : ITokenReplace + { + public const string GenericName = "generic"; + + private const string TokenExpression = "(?:(?\\[\\])|\\[(?:(?[^{}\\]\\[:]+):(?[^\\]\\[\\|]+)|(?[^\\]\\[\\|]+))(?:\\|(?:(?[^\\]\\[]+)\\|(?[^\\]\\\\[]+))|\\|(?:(?[^\\|\\]\\[]+)))?\\])|(?\\[[^\\]\\[]+\\])|(?\\[{0,1}[^\\]\\[]+\\]{0,1})"; + + private Regex TokenizerRegex = new Regex(TokenExpression, RegexOptions.Compiled | RegexOptions.Singleline); + private IDictionary> _tokens; + + private readonly ILogManager _logger; + + public TokenReplace(ILogManager logger) + { + _tokens = new Dictionary>(); + _logger = logger; + } + + public void AddSource(ITokenSource source) + { + this.AddSource(GenericName, source); + } + + public void AddSource(Func> sourceFunc) + { + this.AddSource(GenericName, sourceFunc); + } + + public void AddSource(IDictionary source) + { + this.AddSource(GenericName, source); + } + + public void AddSource(string key, object value) + { + this.AddSource(GenericName, key, value); + } + + public void AddSource(string name, ITokenSource source) + { + var tokens = source.GetTokens(); + this.AddSource(name, tokens); + } + + public void AddSource(string name, Func> sourceFunc) + { + var tokens = sourceFunc(); + this.AddSource(name, tokens); + } + + public void AddSource(string name, IDictionary source) + { + if(source != null) + { + foreach (var key in source.Keys) + { + this.AddSource(name, key, source[key]); + } + } + } + + public void AddSource(string name, string key, object value) + { + if (string.IsNullOrWhiteSpace(name)) + { + name = GenericName; + } + + var source = _tokens.ContainsKey(name.ToLower()) ? _tokens[name.ToLower()] : null; + if(source == null) + { + source = new Dictionary(); + } + source[key] = value; + + _tokens[name.ToLower()] = source; + } + + public string ReplaceTokens(string source) + { + if (string.IsNullOrWhiteSpace(source)) + { + return source; + } + + var result = new StringBuilder(); + foreach (Match match in this.TokenizerRegex.Matches(source)) + { + var key = match.Result("${key}"); + if (!string.IsNullOrWhiteSpace(key)) + { + var sourceName = match.Result("${source}"); + if (string.IsNullOrWhiteSpace(sourceName) || sourceName == "[") + { + sourceName = GenericName; + } + + var format = match.Result("${format}"); + var emptyReplacment = match.Result("${empty}"); + var value = ReplaceTokenValue(sourceName, key, format); + if (string.IsNullOrWhiteSpace(value)) + { + if(!string.IsNullOrWhiteSpace(emptyReplacment)) + { + value = emptyReplacment; + } + else //keep the original content + { + value = match.Value; + } + } + + result.Append(value); + } + else + { + result.Append(match.Result("${text}")); + } + } + + return result.ToString(); + } + + private string ReplaceTokenValue(string sourceName, string key, string format) + { + if(!_tokens.ContainsKey(sourceName.ToLower())) + { + _logger.Log(Shared.LogLevel.Debug, this, Enums.LogFunction.Other, $"MissingSource:{sourceName}"); + return string.Empty; + } + + var tokens = _tokens[sourceName.ToLower()]; + if(!tokens.ContainsKey(key)) + { + _logger.Log(Shared.LogLevel.Debug, this, Enums.LogFunction.Other, $"MissingKey:{key}"); + return string.Empty; + } + + var value = tokens[key]; + if(value == null) + { + return string.Empty; + } + + //TODO: need to implement the format. + return value.ToString(); + } + } +} diff --git a/Oqtane.Shared/Interfaces/ITokenSource.cs b/Oqtane.Shared/Interfaces/ITokenSource.cs new file mode 100644 index 00000000..46fe7f42 --- /dev/null +++ b/Oqtane.Shared/Interfaces/ITokenSource.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Oqtane.Interfaces +{ + public interface ITokenSource + { + IDictionary GetTokens(); + } +}