| | | 1 | | using System.Text.RegularExpressions; |
| | | 2 | | |
| | | 3 | | namespace OzonOrdersWeb.Authorization.Discovery; |
| | | 4 | | |
| | | 5 | | /// <summary> |
| | | 6 | | /// Пытается получить заголовок страницы из Razor-файла: ViewData["Title"] / ViewBag.Title |
| | | 7 | | /// (статическая строка или префикс до { в $"..."). |
| | | 8 | | /// </summary> |
| | | 9 | | public static class RazorViewTitleExtractor |
| | | 10 | | { |
| | | 11 | | public const int MaxTitleLength = 300; |
| | | 12 | | |
| | | 13 | | public static string? TryGetTitleFromConventionalView( |
| | | 14 | | string contentRoot, |
| | | 15 | | string? area, |
| | | 16 | | string controller, |
| | | 17 | | string action) |
| | | 18 | | { |
| | 0 | 19 | | var path = TryResolveConventionalViewPath(contentRoot, area, controller, action); |
| | 0 | 20 | | return path is null ? null : TryExtractTitleFromFile(path); |
| | | 21 | | } |
| | | 22 | | |
| | | 23 | | public static string? TryResolveConventionalViewPath( |
| | | 24 | | string contentRoot, |
| | | 25 | | string? area, |
| | | 26 | | string controller, |
| | | 27 | | string action) |
| | | 28 | | { |
| | 0 | 29 | | var file = action + ".cshtml"; |
| | 0 | 30 | | if (string.IsNullOrEmpty(area)) |
| | | 31 | | { |
| | 0 | 32 | | var p = Path.Combine(contentRoot, "Views", controller, file); |
| | 0 | 33 | | if (File.Exists(p)) return p; |
| | | 34 | | } |
| | | 35 | | else |
| | | 36 | | { |
| | 0 | 37 | | var p = Path.Combine(contentRoot, "Areas", area, "Views", controller, file); |
| | 0 | 38 | | if (File.Exists(p)) return p; |
| | | 39 | | } |
| | | 40 | | |
| | 0 | 41 | | return null; |
| | | 42 | | } |
| | | 43 | | |
| | | 44 | | public static string? TryExtractTitleFromFile(string absolutePath) |
| | | 45 | | { |
| | 0 | 46 | | if (string.IsNullOrEmpty(absolutePath) || !File.Exists(absolutePath)) return null; |
| | 0 | 47 | | var text = File.ReadAllText(absolutePath); |
| | 0 | 48 | | if (text.Length > 64_000) text = text[..64_000]; |
| | 0 | 49 | | return TryExtractTitleFromViewContent(text); |
| | | 50 | | } |
| | | 51 | | |
| | | 52 | | public static string? TryExtractTitleFromViewContent(string text) |
| | | 53 | | { |
| | | 54 | | // 1) ViewData["Title"] = "…" |
| | 0 | 55 | | var m = Regex.Match( |
| | 0 | 56 | | text, |
| | 0 | 57 | | @"ViewData\[\s*""Title""\s*\]\s*=\s*""(?<t>[^""]*)""", |
| | 0 | 58 | | RegexOptions.IgnoreCase); |
| | 0 | 59 | | if (m.Success) return NormalizeTitle(m.Groups["t"].Value); |
| | | 60 | | |
| | 0 | 61 | | m = Regex.Match( |
| | 0 | 62 | | text, |
| | 0 | 63 | | @"ViewData\[\s*'Title'\s*\]\s*=\s*""(?<t>[^""]*)""", |
| | 0 | 64 | | RegexOptions.IgnoreCase); |
| | 0 | 65 | | if (m.Success) return NormalizeTitle(m.Groups["t"].Value); |
| | | 66 | | |
| | | 67 | | // 2) ViewBag.Title = "…" |
| | 0 | 68 | | m = Regex.Match( |
| | 0 | 69 | | text, |
| | 0 | 70 | | @"ViewBag\.Title\s*=\s*""(?<t>[^""]*)""", |
| | 0 | 71 | | RegexOptions.IgnoreCase); |
| | 0 | 72 | | if (m.Success) return NormalizeTitle(m.Groups["t"].Value); |
| | | 73 | | |
| | | 74 | | // 3) $"...": только статический кусок до первой { (или вся строка без {) |
| | 0 | 75 | | m = Regex.Match( |
| | 0 | 76 | | text, |
| | 0 | 77 | | @"ViewData\[\s*""Title""\s*\]\s*=\s*\$""(?<t>[^""{]*)", |
| | 0 | 78 | | RegexOptions.IgnoreCase); |
| | 0 | 79 | | if (m.Success) return NormalizeTitle(m.Groups["t"].Value); |
| | | 80 | | |
| | 0 | 81 | | m = Regex.Match( |
| | 0 | 82 | | text, |
| | 0 | 83 | | @"ViewBag\.Title\s*=\s*\$""(?<t>[^""{]*)", |
| | 0 | 84 | | RegexOptions.IgnoreCase); |
| | 0 | 85 | | if (m.Success) return NormalizeTitle(m.Groups["t"].Value); |
| | | 86 | | |
| | 0 | 87 | | return null; |
| | | 88 | | } |
| | | 89 | | |
| | | 90 | | public static string? NormalizeTitle(string? raw) |
| | | 91 | | { |
| | 0 | 92 | | if (string.IsNullOrWhiteSpace(raw)) return null; |
| | 0 | 93 | | var t = raw.Trim(); |
| | 0 | 94 | | t = t.TrimEnd(' ', '\t', '-', '·', '—', ':'); |
| | 0 | 95 | | if (t.Length == 0) return null; |
| | 0 | 96 | | if (t.Length > MaxTitleLength) t = t[..MaxTitleLength]; |
| | 0 | 97 | | return t; |
| | | 98 | | } |
| | | 99 | | |
| | | 100 | | /// <summary>Текст, который discover создаёт до поиска по представлениям.</summary> |
| | | 101 | | public static bool IsLegacyControllerActionLabel(string controller, string action, string displayName) => |
| | 0 | 102 | | string.Equals(displayName, $"{controller} / {action}", StringComparison.Ordinal); |
| | | 103 | | } |