feat(core): В ShortcodesService добавлена реализация %ТЕГ% и %ТЕГ_СВЯЗИ% для работы с тегами ЭК
- Реализован %ТЕГ:ПРЕФИКС% для извлечения значения тега текущего юнита по правилу "префикс + двоеточие". - Реализован %ТЕГ_СВЯЗИ:ПРЕФИКС% для агрегации тегов связанных юнитов (UnitsInTemplate) с выбором самого популярного значения (сортировка по алфавиту при равенстве частоты). - Добавлены флаги ленивой загрузки UnitTags и UnitsInTemplateTags в ShortcodeDataRequirementsEnum. - Расширена запись TemplateForShortcode и метод PrepareTemplateDataAsync для пакетной подгрузки тегов из базы данных. - Зарегистрированы новые хендлеры в DI-контейнере и в методе GetAvailableShortcodesAsync.
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using PARR.Core.Services.Shortcodes.Enums;
|
||||
using PARR.Core.Services.Shortcodes.Models;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace PARR.Core.Services.Shortcodes.Handlers;
|
||||
|
||||
internal class RelatedUnitTagShortcodeHandler : IShortcodeHandler
|
||||
{
|
||||
private readonly ILogger<RelatedUnitTagShortcodeHandler> logger;
|
||||
|
||||
public int Order => 75;
|
||||
|
||||
private static readonly Regex _pattern = new(@"%ТЕГ_СВЯЗИ:([^%]+)%", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
public Regex Pattern => _pattern;
|
||||
|
||||
public ShortcodeDataRequirementsEnum Requirements => ShortcodeDataRequirementsEnum.UnitsInTemplateTags;
|
||||
|
||||
public RelatedUnitTagShortcodeHandler(ILogger<RelatedUnitTagShortcodeHandler> logger)
|
||||
{
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
public Task<string> ResolveAsync(string input, TemplateForShortcode data, string caller, CancellationToken ct = default)
|
||||
{
|
||||
var matches = Pattern.Matches(input);
|
||||
if (matches.Count == 0) return Task.FromResult(input);
|
||||
|
||||
var uniqueTags = matches.Cast<Match>()
|
||||
.Select(m => m.Groups[1].Value.Trim())
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||
.ToList();
|
||||
|
||||
var resolved = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var tagName in uniqueTags)
|
||||
{
|
||||
resolved[$"%ТЕГ_СВЯЗИ:{tagName}%"] = GetMostPopularTagValue(data.RelatedUnitTags, tagName, caller);
|
||||
}
|
||||
|
||||
var result = input;
|
||||
foreach (Match m in matches)
|
||||
{
|
||||
if (resolved.TryGetValue(m.Value, out var val))
|
||||
{
|
||||
result = result.Replace(m.Value, val, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
|
||||
return Task.FromResult(result);
|
||||
}
|
||||
|
||||
private string GetMostPopularTagValue(Dictionary<Guid, List<string>>? relatedTags, string tagName, string caller)
|
||||
{
|
||||
if (relatedTags == null || relatedTags.Count == 0)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var prefix = tagName + ":";
|
||||
var matchedValues = new List<string>();
|
||||
|
||||
foreach (var tagList in relatedTags.Values)
|
||||
{
|
||||
foreach (var tag in tagList)
|
||||
{
|
||||
string? matchedValue = null;
|
||||
|
||||
if (tag.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
matchedValue = tag.Substring(prefix.Length);
|
||||
}
|
||||
else if (tag.Equals(tagName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
matchedValue = tag;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(matchedValue))
|
||||
{
|
||||
matchedValues.Add(matchedValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (matchedValues.Count == 0)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var mostPopular = matchedValues
|
||||
.GroupBy(v => v, StringComparer.OrdinalIgnoreCase)
|
||||
.Select(g => new { Value = g.Key, Count = g.Count() })
|
||||
.OrderByDescending(x => x.Count)
|
||||
.ThenBy(x => x.Value, StringComparer.OrdinalIgnoreCase)
|
||||
.FirstOrDefault()!;
|
||||
|
||||
return mostPopular.Value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using PARR.Core.Services.Shortcodes.Enums;
|
||||
using PARR.Core.Services.Shortcodes.Models;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace PARR.Core.Services.Shortcodes.Handlers;
|
||||
|
||||
internal class TagShortcodeHandler : IShortcodeHandler
|
||||
{
|
||||
private readonly ILogger<TagShortcodeHandler> logger;
|
||||
|
||||
public int Order => 70;
|
||||
|
||||
private static readonly Regex _pattern = new(@"%ТЕГ:([^%]+)%", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
public Regex Pattern => _pattern;
|
||||
|
||||
public ShortcodeDataRequirementsEnum Requirements => ShortcodeDataRequirementsEnum.UnitTags;
|
||||
|
||||
public TagShortcodeHandler(ILogger<TagShortcodeHandler> logger)
|
||||
{
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
public Task<string> ResolveAsync(string input, TemplateForShortcode data, string caller, CancellationToken ct = default)
|
||||
{
|
||||
var matches = Pattern.Matches(input);
|
||||
if (matches.Count == 0) return Task.FromResult(input);
|
||||
|
||||
var uniqueTags = matches.Cast<Match>()
|
||||
.Select(m => m.Groups[1].Value.Trim())
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||
.ToList();
|
||||
|
||||
var resolved = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var tagName in uniqueTags)
|
||||
{
|
||||
resolved[$"%ТЕГ:{tagName}%"] = GetTagValue(data.UnitTags, tagName, caller);
|
||||
}
|
||||
|
||||
var result = input;
|
||||
foreach (Match m in matches)
|
||||
{
|
||||
if (resolved.TryGetValue(m.Value, out var val))
|
||||
{
|
||||
result = result.Replace(m.Value, val, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
|
||||
return Task.FromResult(result);
|
||||
}
|
||||
|
||||
private string GetTagValue(List<string>? unitTags, string tagName, string caller)
|
||||
{
|
||||
if (unitTags == null || unitTags.Count == 0)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var prefix = tagName + ":";
|
||||
// Ищем тег, который начинается с префикса и двоеточия (например, "БД_в_ЗО_РГ:")
|
||||
var matchedTag = unitTags.FirstOrDefault(t => t.StartsWith(prefix, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (matchedTag != null)
|
||||
{
|
||||
// Возвращаем часть строки после двоеточия
|
||||
return matchedTag.Substring(prefix.Length);
|
||||
}
|
||||
|
||||
// Резервный вариант: если тег совпадает полностью без двоеточия
|
||||
var exactTag = unitTags.FirstOrDefault(t => t.Equals(tagName, StringComparison.OrdinalIgnoreCase));
|
||||
if (exactTag != null)
|
||||
{
|
||||
return exactTag;
|
||||
}
|
||||
|
||||
logger.LogDebug("[{Caller}] Тег с префиксом '{Prefix}' не найден в UnitTags.", caller, prefix);
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user