feat: 项目初始化 + 3D方块世界原型 + AI助搭系统
CI / Go Backend (push) Canceled after 0s

初始化 monorepo: Go后端(7微服务) + Unity客户端(9模块) + 启动器

HTML5原型: Three.js 3D体素世界, Perlin噪声地形, 原版材质, 22种方块

Minecraft创造模式背包: 双栏布局, 拖拽移动物品, 方向性元件引脚

AI助搭策划文档 + 客户端/服务端骨架 + Docker Compose + CI
This commit is contained in:
xyou
2026-08-08 14:07:56 +08:00
parent 9500c4c80a
commit f70b061d1a
1972 changed files with 159760 additions and 6 deletions
@@ -0,0 +1,369 @@
using System.IO;
using PCL.Core.App;
using PCL.Core.App.Configuration;
using PCL.Core.App.Localization;
using PCL.Core.Utils.OS;
namespace PCL
{
public class CustomEvent
{
public EventType Type { get; set; } = EventType.None;
public string Data { get; set; } = string.Empty;
public CustomEvent() { }
public CustomEvent(EventType type, string data)
{
Type = type;
Data = data;
}
public void Raise() => Raise(Type, Data);
/// <summary>
/// 静态入口:根据 EventType 分发给对应的 action 执行。
/// </summary>
public static void Raise(EventType type, string arg)
{
if (type == EventType.None) return;
ModBase.Log($"[Control] 执行自定义事件: {type}, {arg}");
try
{
if (ActionMap.TryGetValue(type, out var action))
action(arg, type);
else
ModMain.MyMsgBox(
Lang.Text("Event.Error.UnknownType", type.ToString()),
Lang.Text("Event.Error.Title"));
}
catch (Exception ex)
{
ModBase.Log(
ex,
Lang.Text("Event.Error.ExecutionFailed", type, arg),
ModBase.LogLevel.Msgbox,
userSummary: Lang.Text("Event.Error.ExecutionFailed", type, arg));
}
}
public static string GetCustomVariable(string name, string defaultValue = "") =>
States.CustomVariables.GetValueOrDefault(name, defaultValue);
/// <summary>
/// 将 arg 按 '|' 分割为参数数组,空串统一返回 [""]。
/// </summary>
private static string[] SplitArgs(string arg) => arg.Split('|');
/// <summary>
/// 将 \\n 替换为 Windows 换行符 \r\n。
/// </summary>
private static string FixNewlines(string s) => s.Replace("\\n", "\r\n");
/// <summary>
/// 禁止自定义主页写入的高危设置黑名单
/// </summary>
private static readonly HashSet<string> SecuritySensitiveSettingKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
"LaunchAdvanceRun",
"VersionAdvanceRun"
};
/// <summary>
/// EventType → 执行逻辑 的字典映射,O(1) 分发。
/// </summary>
private static readonly Dictionary<EventType, Action<string, EventType>> ActionMap = new()
{
[EventType.OpenUrl] = _OpenUrl,
[EventType.OpenFile] = _OpenFileOrCommand,
[EventType.ExecuteCommand] = _OpenFileOrCommand,
[EventType.LaunchGame] = _LaunchGame,
[EventType.CopyText] = _CopyText,
[EventType.RefreshHomepage] = _Refresh,
[EventType.RefreshPage] = _Refresh,
[EventType.DailyFortune] = _DailyFortune,
[EventType.ClearTrash] = _ClearTrash,
[EventType.ShowDialog] = _ShowDialog,
[EventType.ShowHint] = _ShowHint,
[EventType.SwitchPage] = _SwitchPage,
[EventType.ImportModpack] = _ModpackInstall,
[EventType.InstallModpack] = _ModpackInstall,
[EventType.DownloadFile] = _DownloadFile,
[EventType.ModifySetting] = _WriteSetting,
[EventType.WriteSetting] = _WriteSetting,
[EventType.ModifyVariable] = _WriteVariable,
[EventType.WriteVariable] = _WriteVariable,
[EventType.OpenHelp] = (_, __) => ModBase.OpenWebsite("https://docs.pclc.cc/ce"),
};
/// <summary>
/// 打开网页。校验 https?:// 前缀,非 file 协议。
/// </summary>
private static void _OpenUrl(string arg, EventType type)
{
arg = arg.Replace('\\', '/');
if (!arg.Contains("://") || arg.StartsWithF("file", true))
{
ModMain.MyMsgBox(Lang.Text("Event.Error.UrlRequired"), Lang.Text("Event.Error.Title"));
return;
}
HintService.Hint(Lang.Text("Event.OpenUrl.Opening", arg));
ModBase.RunInThread(() => ModBase.OpenWebsite(arg));
}
/// <summary>
/// 打开文件 / 执行命令。复用 GetAbsoluteUrls 解析路径后 ProcessInterop.Start 启动。
/// </summary>
private static void _OpenFileOrCommand(string arg, EventType type)
{
var args = SplitArgs(arg);
ModBase.RunInThread(() =>
{
try
{
var urls = GetAbsoluteUrls(args[0], type);
if (!EventSafetyConfirm($"{urls[0]}{(args.Length >= 2 ? " " + args[1] : "")}"))
return;
ProcessInterop.Start(urls[0], args.Length >= 2 ? args[1] : "");
}
catch (Exception ex)
{
ModBase.Log(
ex,
Lang.Text("Event.Error.ExecutionFailed", type, arg),
ModBase.LogLevel.Msgbox,
userSummary: Lang.Text("Event.Error.ExecutionFailed", type, arg));
}
});
}
/// <summary>
/// 启动游戏。支持 \current 指代当前选中实例,可选 ServerIp。
/// </summary>
private static void _LaunchGame(string arg, EventType type)
{
var args = SplitArgs(arg);
if (args[0] == "\\current")
{
if (ModInstanceList.McMcInstanceSelected is null)
throw new InvalidOperationException(Lang.Text("Event.LaunchGame.SelectVersion"));
args[0] = ModInstanceList.McMcInstanceSelected.Name;
}
ModBase.RunInUi(() =>
{
var launchOptions = new ModLaunch.McLaunchOptions
{
ServerIp = args.Length >= 2 ? args[1] : null,
instance = new McInstance(args[0])
};
if (ModLaunch.McLaunchStart(launchOptions))
HintService.Hint(Lang.Text("Event.LaunchGame.Starting", args[0]));
});
}
/// <summary>
/// 复制文本到剪贴板。
/// </summary>
private static void _CopyText(string arg, EventType _) => ModBase.ClipboardSet(arg);
/// <summary>
/// 刷新主页 / 刷新当前页面。要求当前 pageRight 实现 IRefreshable。
/// </summary>
private static void _Refresh(string arg, EventType _)
{
if (ModMain.frmMain?.pageRight is IRefreshable refreshable)
{
ModBase.RunInUiWait(() => refreshable.Refresh());
if (string.IsNullOrEmpty(arg))
HintService.Hint(Lang.Text("Event.Refresh.Success"), HintType.Success);
}
else
HintService.Hint(Lang.Text("Event.Refresh.NotSupported"), HintType.Error);
}
/// <summary>
/// 今日人品。
/// </summary>
private static void _DailyFortune(string _, EventType __) => PageToolsTest.Jrrp();
/// <summary>
/// 清理垃圾。异步执行 RubbishClear。
/// </summary>
private static void _ClearTrash(string _, EventType __) =>
ModBase.RunInThread(PageToolsTest.RubbishClear);
/// <summary>
/// 弹出消息框。参数:Title|Content[|ButtonText]。
/// </summary>
private static void _ShowDialog(string arg, EventType type)
{
var args = SplitArgs(arg);
if (args.Length == 1)
throw new ArgumentException(Lang.Text("Event.Error.MissingArgs", type.ToString(), "Title|Content"));
ModMain.MyMsgBox(
FixNewlines(args[1]),
FixNewlines(args[0]),
args.Length > 2 ? args[2] : Lang.Text("Common.Action.Confirm"));
}
/// <summary>
/// 弹出提示条。参数:Message[|HintType]HintType = Info / Success / Error,兼容旧版 Finish / Critical)。
/// </summary>
private static void _ShowHint(string arg, EventType _)
{
var args = SplitArgs(arg);
var hintType = args.Length == 1
? HintType.Info
: (HintType)Enum.Parse(typeof(HintType), NormalizeHintTypeName(args[1]), true);
HintService.Hint(FixNewlines(args[0]), hintType);
}
/// <summary>
/// 将旧版 HintType 名称映射到当前名称,保持对旧主页的兼容。
/// </summary>
private static string NormalizeHintTypeName(string name) => name.ToLowerInvariant() switch
{
"finish" => nameof(HintType.Success),
"critical" => nameof(HintType.Error),
_ => name
};
/// <summary>
/// 切换页面。参数:PageType[|PageSubType]。
/// </summary>
private static void _SwitchPage(string arg, EventType _)
{
var args = SplitArgs(arg);
ModBase.RunInUi(() =>
{
var page = (FormMain.PageType)Enum.Parse(typeof(FormMain.PageType), args[0], true);
var sub = args.Length == 1
? FormMain.PageSubType.Default
: (FormMain.PageSubType)Enum.Parse(typeof(FormMain.PageSubType), args[1], true);
ModMain.frmMain?.PageChange(page, sub);
});
}
/// <summary>
/// 导入 / 安装整合包。触发 ModModpack.ModpackInstall()。
/// </summary>
private static void _ModpackInstall(string _, EventType __) =>
ModBase.RunInUi(ModModpack.ModpackInstall);
/// <summary>
/// 下载文件。参数:Url[|SavePath[|FileName]],校验 http/https 前缀并弹安全确认。
/// </summary>
private static void _DownloadFile(string arg, EventType _)
{
var args = SplitArgs(arg);
args[0] = args[0].Replace('\\', '/');
if (!args[0].StartsWithF("http://", true) && !args[0].StartsWithF("https://", true))
{
ModMain.MyMsgBox(Lang.Text("Event.Error.DownloadUrlRequired"), Lang.Text("Event.Error.Title"));
return;
}
if (!EventSafetyConfirm(Lang.Text("Event.Download.Confirm", args[0])))
return;
try
{
PageToolsTest.StartCustomDownload(args[0],
args.Length >= 2 ? args[1] : ModBase.GetFileNameFromPath(args[0]),
args.Length >= 3 ? args[2] : null);
}
catch
{
PageToolsTest.StartCustomDownload(args[0], Lang.Text("Common.State.Unknown"));
}
}
/// <summary>
/// 写入 / 修改设置。参数:SettingName|Value。
/// </summary>
private static void _WriteSetting(string arg, EventType type)
{
var args = SplitArgs(arg);
if (args.Length == 1)
throw new ArgumentException(Lang.Text("Event.Error.MissingArgs", type.ToString(), "SettingName|Value"));
if (ConfigService.TryGetConfigItemNoType(args[0], out var item) && item.Source != ConfigSource.SharedEncrypt)
{
if (!CanWriteSettingFromCustomEvent(args[0]))
return;
item.SetValueNoType(args[1], ModInstanceList.McMcInstanceSelected?.PathInstance);
}
if (args.Length == 2)
HintService.Hint(Lang.Text("Event.Setting.Written", args[0], args[1]), HintType.Success);
}
private static bool CanWriteSettingFromCustomEvent(string key)
{
if (!SecuritySensitiveSettingKeys.Contains(key))
return true;
ModBase.Log($"[Control] 已阻止自定义事件写入高危设置:{key}", ModBase.LogLevel.Developer);
HintService.Hint(Lang.Text("Event.Safety.DangerousSettingBlocked", key), HintType.Error);
return false;
}
/// <summary>
/// 写入 / 修改自定义变量。参数:VariableName|Value。
/// </summary>
private static void _WriteVariable(string arg, EventType type)
{
var args = SplitArgs(arg);
if (args.Length == 1)
throw new ArgumentException(Lang.Text("Event.Error.MissingArgs", type.ToString(), "VariableName|Value"));
States.CustomVariables[args[0]] = args[1];
States.CustomVariables = States.CustomVariables; // 触发 ConfigPropertyChanged
if (args.Length == 2)
HintService.Hint(Lang.Text("Event.Variable.Written", args[0], args[1]), HintType.Success);
}
public static string[] GetAbsoluteUrls(string relativeUrl, EventType type)
{
relativeUrl = relativeUrl.Replace('/', '\\').ToLower().TrimStart('\\');
var pclDir = Path.Combine(Basics.ExecutableDirectory, "PCL");
if (relativeUrl.Contains(":\\"))
{
ModBase.Log($"[Control] 自定义事件中由绝对路径 {type}: {relativeUrl}");
return [relativeUrl, pclDir];
}
if (File.Exists(Path.Combine(pclDir, relativeUrl)))
{
var fullPath = Path.Combine(pclDir, relativeUrl);
var resolved = Path.GetFullPath(fullPath);
if (!resolved.StartsWith(pclDir, StringComparison.OrdinalIgnoreCase))
throw new UnauthorizedAccessException(Lang.Text("Event.Error.FileNotFound", relativeUrl));
ModBase.Log($"[Control] 自定义事件中由相对 PCL 文件夹的路径 {type}: {fullPath}");
return [fullPath, pclDir];
}
if (type is EventType.OpenFile or EventType.ExecuteCommand)
{
ModBase.Log($"[Control] 自定义事件中直接 {type}: {relativeUrl}");
return [relativeUrl, pclDir];
}
throw new FileNotFoundException(Lang.Text("Event.Error.FileNotFound", relativeUrl), relativeUrl);
}
/// <summary>
/// 安全确认对话框。已由用户勾选"不再询问"时跳过。
/// </summary>
private static bool EventSafetyConfirm(string message)
{
if (States.Hint.HomepageCommand)
return true;
return ModMain.MyMsgBox(
Lang.Text("Event.Safety.ConfirmMessage", message),
Lang.Text("Event.Safety.Title"),
Lang.Text("Common.Action.Continue"),
Lang.Text("Event.Safety.ContinueAlways"),
Lang.Text("Common.Action.Cancel")) switch
{
1 => true,
2 => States.Hint.HomepageCommand = true,
_ => false,
};
}
}
}
@@ -0,0 +1,16 @@
using System.Collections;
using System.Windows.Markup;
namespace PCL
{
[ContentProperty("Events")]
public class CustomEventCollection : IEnumerable<CustomEvent>
{
private readonly List<CustomEvent> _events = new();
public List<CustomEvent> Events => _events;
public IEnumerator<CustomEvent> GetEnumerator() => Events.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
}
@@ -0,0 +1,56 @@
using System.Windows;
namespace PCL
{
public static class CustomEventService
{
public static readonly DependencyProperty EventsProperty =
DependencyProperty.RegisterAttached(
"Events",
typeof(CustomEventCollection),
typeof(CustomEventService),
new PropertyMetadata(null));
[AttachedPropertyBrowsableForType(typeof(DependencyObject))]
public static void SetEvents(DependencyObject d, CustomEventCollection value) =>
d.SetValue(EventsProperty, value);
[AttachedPropertyBrowsableForType(typeof(DependencyObject))]
public static CustomEventCollection GetEvents(DependencyObject d)
{
if (d.GetValue(EventsProperty) is null)
d.SetValue(EventsProperty, new CustomEventCollection());
return (CustomEventCollection)d.GetValue(EventsProperty);
}
public static readonly DependencyProperty EventTypeProperty =
DependencyProperty.RegisterAttached(
"EventType",
typeof(EventType),
typeof(CustomEventService),
new PropertyMetadata(EventType.None));
[AttachedPropertyBrowsableForType(typeof(DependencyObject))]
public static void SetEventType(DependencyObject d, EventType value) =>
d.SetValue(EventTypeProperty, value);
[AttachedPropertyBrowsableForType(typeof(DependencyObject))]
public static EventType GetEventType(DependencyObject d) =>
(EventType)d.GetValue(EventTypeProperty);
public static readonly DependencyProperty EventDataProperty =
DependencyProperty.RegisterAttached(
"EventData",
typeof(string),
typeof(CustomEventService),
new PropertyMetadata(null));
[AttachedPropertyBrowsableForType(typeof(DependencyObject))]
public static void SetEventData(DependencyObject d, string value) =>
d.SetValue(EventDataProperty, value);
[AttachedPropertyBrowsableForType(typeof(DependencyObject))]
public static string GetEventData(DependencyObject d) =>
(string)d.GetValue(EventDataProperty);
}
}
@@ -0,0 +1,27 @@
namespace PCL
{
public enum EventType
{
None = 0,
OpenUrl,
OpenFile,
ExecuteCommand,
LaunchGame,
CopyText,
RefreshHomepage,
RefreshPage,
DailyFortune,
ClearTrash,
ShowDialog,
ShowHint,
SwitchPage,
ImportModpack,
InstallModpack,
DownloadFile,
ModifySetting,
WriteSetting,
ModifyVariable,
WriteVariable,
OpenHelp
}
}
@@ -0,0 +1,57 @@
namespace PCL
{
public static class EventTypeMapper
{
private static readonly Dictionary<string, string> ChineseToEnglish = new()
{
{ "打开网页", "OpenUrl" },
{ "打开文件", "OpenFile" },
{ "执行命令", "ExecuteCommand" },
{ "启动游戏", "LaunchGame" },
{ "复制文本", "CopyText" },
{ "刷新主页", "RefreshHomepage" },
{ "刷新页面", "RefreshPage" },
{ "今日人品", "DailyFortune" },
{ "清理垃圾", "ClearTrash" },
{ "弹出窗口", "ShowDialog" },
{ "弹出提示", "ShowHint" },
{ "切换页面", "SwitchPage" },
{ "导入整合包", "ImportModpack" },
{ "安装整合包", "InstallModpack" },
{ "下载文件", "DownloadFile" },
{ "修改设置", "ModifySetting" },
{ "写入设置", "WriteSetting" },
{ "修改变量", "ModifyVariable" },
{ "写入变量", "WriteVariable" },
{ "打开帮助", "OpenHelp" },
{ "刷新帮助", "OpenHelp" },
};
public static readonly HashSet<string> UnsupportedTypes = new(StringComparer.Ordinal)
{
"内存优化", "加入房间", "检查更新"
};
public static bool TryToEnglish(string chineseName, out string englishName) =>
ChineseToEnglish.TryGetValue(chineseName, out englishName);
public static bool IsUnsupportedType(string name) =>
UnsupportedTypes.Contains(name);
public static bool TryParse(string value, out EventType result)
{
result = EventType.None;
if (string.IsNullOrEmpty(value))
return false;
if (Enum.TryParse(value, true, out result))
return true;
if (TryToEnglish(value, out var englishName))
return Enum.TryParse(englishName, true, out result);
return false;
}
}
}
@@ -0,0 +1,216 @@
using System.Text.RegularExpressions;
namespace PCL
{
public static partial class XamlEventSanitizer
{
public class SanitizeResult
{
public string SanitizedXaml { get; set; } = "";
public List<string> UnsupportedTypesFound { get; set; } = new();
public List<string> UnrecognizedTypes { get; set; } = new();
}
[GeneratedRegex(@"(local:CustomEventService\.EventType\s*=\s*"")([^""]+)("")")]
private static partial Regex EventTypeAttributeRegex();
[GeneratedRegex(@"(<local:CustomEventService\.EventType\s*>\s*)([^<]+?)(\s*</local:CustomEventService\.EventType\s*>)")]
private static partial Regex EventTypePropertyElementRegex();
[GeneratedRegex(@"(<local:CustomEvent\.Type\s*>\s*)([^<]+?)(\s*</local:CustomEvent\.Type\s*>)")]
private static partial Regex CustomEventTypePropertyElementRegex();
[GeneratedRegex(@"(<local:CustomEvent\s+[^>]*?\bType\s*=\s*"")([^""]+)("")")]
private static partial Regex LocalCustomEventTypeAttributeRegex();
[GeneratedRegex(@"<local:CustomEvent\s+[^>]*?\bType\s*=\s*""([^""]+)""[^>]*/\s*>")]
private static partial Regex LocalCustomEventSelfClosingRegex();
[GeneratedRegex(@"<local:CustomEvent\s+[^>]*?\bType\s*=\s*""([^""]+)""[^>]*>")]
private static partial Regex LocalCustomEventOpenTagRegex();
[GeneratedRegex(@"<Setter\b[^>]*?\bProperty\s*=\s*""local:CustomEventService\.EventType""[^>]*?\bValue\s*=\s*""([^""]+)""[^>]*(?:/\s*>|>\s*</Setter\s*>)")]
private static partial Regex SetterEventTypePropertyFirstRegex();
[GeneratedRegex(@"<Setter\b[^>]*?\bValue\s*=\s*""([^""]+)""[^>]*?\bProperty\s*=\s*""local:CustomEventService\.EventType""[^>]*(?:/\s*>|>\s*</Setter\s*>)")]
private static partial Regex SetterEventTypeValueFirstRegex();
public static SanitizeResult Sanitize(string xaml)
{
var result = new SanitizeResult();
var sanitized = xaml;
sanitized = EventTypeAttributeRegex().Replace(sanitized, match =>
{
var rawValue = match.Groups[2].Value;
return ReplaceEventType(match, rawValue, result);
});
sanitized = EventTypePropertyElementRegex().Replace(sanitized, match =>
{
var rawValue = match.Groups[2].Value.Trim();
return ReplaceEventType(match, rawValue, result);
});
sanitized = CustomEventTypePropertyElementRegex().Replace(sanitized, match =>
{
var rawValue = match.Groups[2].Value.Trim();
return ReplaceOrRemovePropertyElement(match, rawValue, result);
});
sanitized = LocalCustomEventTypeAttributeRegex().Replace(sanitized, match =>
{
var rawValue = match.Groups[2].Value;
return ReplaceEventType(match, rawValue, result);
});
sanitized = LocalCustomEventSelfClosingRegex().Replace(sanitized, match =>
{
var rawValue = match.Groups[1].Value;
return _RemoveBadCustomEventElement(match, rawValue, result);
});
sanitized = LocalCustomEventOpenTagRegex().Replace(sanitized, match =>
{
var rawValue = match.Groups[1].Value;
if (_IsBadType(rawValue, result))
{
var elementName = "local:CustomEvent";
var afterTag = sanitized[(match.Index + match.Length)..];
var closeLen = FindMatchingCloseTag(afterTag, elementName);
if (closeLen < 0) return match.Value;
return afterTag[closeLen..];
}
return match.Value;
});
sanitized = SetterEventTypePropertyFirstRegex().Replace(sanitized, match =>
_ReplaceSetter(match, match.Groups[1].Value, result));
sanitized = SetterEventTypeValueFirstRegex().Replace(sanitized, match =>
_ReplaceSetter(match, match.Groups[1].Value, result));
RemoveElementsForTypes(result.UnsupportedTypesFound, ref sanitized);
RemoveElementsForTypes(result.UnrecognizedTypes, ref sanitized);
result.UnsupportedTypesFound = result.UnsupportedTypesFound.Distinct().ToList();
result.UnrecognizedTypes = result.UnrecognizedTypes.Distinct().ToList();
result.SanitizedXaml = sanitized;
return result;
}
private static bool _IsBadType(string rawValue, SanitizeResult result)
{
if (EventTypeMapper.IsUnsupportedType(rawValue))
{
result.UnsupportedTypesFound.Add(rawValue);
return true;
}
if (!Enum.TryParse<EventType>(rawValue, true, out _)
&& !EventTypeMapper.TryToEnglish(rawValue, out _))
{
result.UnrecognizedTypes.Add(rawValue);
return true;
}
return false;
}
private static string _RemoveBadCustomEventElement(
Match match, string rawValue, SanitizeResult result)
{
return _IsBadType(rawValue, result) ? "" : match.Value;
}
private static void RemoveElementsForTypes(List<string> types, ref string sanitized)
{
var snapshot = types.ToList();
foreach (var type in snapshot)
sanitized = RemoveElementsWithEventType(sanitized, type, types);
}
private static string ReplaceEventType(Match match, string rawValue, SanitizeResult result)
{
if (EventTypeMapper.TryToEnglish(rawValue, out var englishName))
return match.Groups[1].Value + englishName + match.Groups[3].Value;
if (Enum.TryParse<EventType>(rawValue, true, out _))
return match.Value;
if (EventTypeMapper.IsUnsupportedType(rawValue))
{
result.UnsupportedTypesFound.Add(rawValue);
return match.Value;
}
result.UnrecognizedTypes.Add(rawValue);
return match.Value;
}
private static string ReplaceOrRemovePropertyElement(
Match match, string rawValue, SanitizeResult result)
{
if (_IsBadType(rawValue, result))
return "";
return ReplaceEventType(match, rawValue, result);
}
private static string _ReplaceSetter(Match match, string rawValue, SanitizeResult result)
{
if (_IsBadType(rawValue, result))
return "";
if (EventTypeMapper.TryToEnglish(rawValue, out var englishName))
return match.Value.Replace($"Value=\"{rawValue}\"", $"Value=\"{englishName}\"");
return match.Value;
}
private static string RemoveElementsWithEventType(string xaml, string eventTypeValue, List<string> trackingList)
{
var escaped = Regex.Escape(eventTypeValue);
var selfClosingPattern = $@"<[\w:]+[^>]*\s+local:CustomEventService\.EventType\s*=\s*""{escaped}""[^>]*/\s*>";
xaml = Regex.Replace(xaml, selfClosingPattern, match =>
{
trackingList.Add(eventTypeValue);
return "";
}, RegexOptions.Compiled);
var openTagPattern = $@"<[\w:]+[^>]*\s+local:CustomEventService\.EventType\s*=\s*""{escaped}""[^>]*>";
xaml = Regex.Replace(xaml, openTagPattern, match =>
{
var elementName = Regex.Match(match.Value, @"<([\w:]+)").Groups[1].Value;
var afterTag = xaml[(match.Index + match.Length)..];
var closeLen = FindMatchingCloseTag(afterTag, elementName);
if (closeLen < 0) return match.Value;
trackingList.Add(eventTypeValue);
return afterTag[closeLen..];
}, RegexOptions.Compiled);
var propertyElementPattern = $@"<local:CustomEventService\.EventType\s*>\s*{escaped}\s*</local:CustomEventService\.EventType\s*>";
xaml = Regex.Replace(xaml, propertyElementPattern, match =>
{
var beforeMatch = xaml[..match.Index];
var lastOpenMatch = Regex.Match(beforeMatch, @"<([\w:]+)[^>]*>\s*$", RegexOptions.RightToLeft);
if (!lastOpenMatch.Success) return match.Value;
var parentElementName = lastOpenMatch.Groups[1].Value;
var afterProperty = xaml[(match.Index + match.Length)..];
var parentCloseMatch = Regex.Match(afterProperty, $@"</{parentElementName}\s*>");
if (!parentCloseMatch.Success) return match.Value;
trackingList.Add(eventTypeValue);
return "";
}, RegexOptions.Compiled);
return xaml;
}
private static int FindMatchingCloseTag(string text, string elementName)
{
var closePattern = $@"</{elementName}\s*>";
var closeMatch = Regex.Match(text, closePattern);
return closeMatch.Success ? closeMatch.Index + closeMatch.Length : -1;
}
}
}