namespace QWERTYkez.WordProcessor; /// /// Динамическая замена параграфа, содержащего "$key|param$", на таблицу. /// Логика построения и подмены — 1:1 копия из , /// плюс извлечение параметра. /// internal static class DynamicReplaceToTableExt { /// /// Заменяет все параграфы, содержащие шаблон "$key|...$", на таблицы. /// /// Делегат (param, tableBuilder) — параметр из "$key|param$" и билдер таблицы. internal static void ReplaceParagraphsContainingTextToTableDynamic( Body body, string oldValue, Action buildTable, FontProps? baseFont = null) { if (body is null || string.IsNullOrEmpty(oldValue) || buildTable is null) return; DynamicReplaceExt.ParsePattern(oldValue, out var prefix, out var suffix); var paragraphs = body.Elements().ToList(); if (paragraphs.Count == 0) return; for (int i = paragraphs.Count - 1; i >= 0; i--) { var paragraph = paragraphs[i]; if (paragraph is null) continue; var paraText = paragraph.InnerText; if (string.IsNullOrEmpty(paraText)) continue; int pStart = paraText.IndexOf(prefix, StringComparison.OrdinalIgnoreCase); if (pStart < 0) continue; int paramStart = pStart + prefix.Length; int pEnd = string.IsNullOrEmpty(suffix) ? paraText.Length : paraText.IndexOf(suffix, paramStart, StringComparison.OrdinalIgnoreCase); if (pEnd < 0) continue; string param = paraText.Substring(paramStart, pEnd - paramStart); string matchedText = paraText.Substring(pStart, pEnd + suffix.Length - pStart); // Извлекаем свойства шрифта из Run-а, содержащего совпадение var fontProps = ExtractFontPropsFromParagraph(paragraph, matchedText); if (fontProps is { } props) fontProps = props.Override(baseFont); else if (baseFont is not null) fontProps = baseFont; ReplaceWithTableBuilderDynamic(paragraph, param, buildTable, fontProps); } } /// /// Создаёт TableBuilder, вызывает пользовательский делегат, заменяет параграф таблицей. /// (Копия с добавленным param.) /// private static void ReplaceWithTableBuilderDynamic( Paragraph paragraph, string param, Action buildTable, FontProps? baseFont) { if (paragraph is null || paragraph.Parent is null || buildTable is null) return; var parent = paragraph.Parent; var builder = TableBuilder.Create(baseFont); buildTable(param, builder); var table = builder.Build(); int paraIndex = -1; var children = parent.ChildElements; for (int i = 0; i < children.Count; i++) { if (children[i] == paragraph) { paraIndex = i; break; } } if (paraIndex == -1) return; parent.RemoveChild(paragraph); parent.InsertAt(table, paraIndex); } private static FontProps? ExtractFontPropsFromParagraph(Paragraph paragraph, string searchText) { if (paragraph is null || string.IsNullOrEmpty(searchText)) return null; var runs = paragraph.Descendants().ToList(); if (runs.Count == 0) return null; var runInfos = new List<(Run Run, int Start, int End, string Text)>(); int currentPosition = 0; foreach (var run in runs) { var runText = GetRunText(run); if (!string.IsNullOrEmpty(runText)) { runInfos.Add((run, currentPosition, currentPosition + runText.Length, runText)); currentPosition += runText.Length; } } foreach (var (run, start, end, text) in runInfos) { if (text.IndexOf(searchText, StringComparison.OrdinalIgnoreCase) >= 0) return CreateFontPropsFromRun(run); } var fullText = string.Concat(runInfos.Select(r => r.Text)); int textIndex = fullText.IndexOf(searchText, StringComparison.OrdinalIgnoreCase); if (textIndex >= 0) { var intersectingRun = runInfos.FirstOrDefault(r => r.Start <= textIndex && r.End > textIndex); if (intersectingRun.Run is not null) return CreateFontPropsFromRun(intersectingRun.Run); } return null; } private static FontProps CreateFontPropsFromRun(Run run) { var runProperties = run.RunProperties; if (runProperties is null) return new FontProps(); var fontProps = new FontProps(); var runFonts = runProperties.GetFirstChild(); if (runFonts is not null) { fontProps = fontProps with { FontFamily = runFonts.Ascii ?? runFonts.HighAnsi ?? runFonts.ComplexScript }; } var fontSize = runProperties.GetFirstChild(); if (fontSize is not null && !string.IsNullOrEmpty(fontSize.Val) && uint.TryParse(fontSize.Val, out uint halfPoints)) { fontProps = fontProps with { Size = halfPoints / 2.0 }; } var bold = runProperties.GetFirstChild(); if (bold is not null) { bool isBold = true; if (bold.Val is not null) isBold = bold.Val.Value; fontProps = fontProps with { IsBold = isBold }; } var italic = runProperties.GetFirstChild(); if (italic is not null) { bool isItalic = true; if (italic.Val is not null) isItalic = italic.Val.Value; fontProps = fontProps with { IsItalic = isItalic }; } var underline = runProperties.GetFirstChild(); if (underline is not null && underline.Val is not null) { fontProps = fontProps with { Underline = underline.Val.Value }; } var color = runProperties.GetFirstChild(); if (color is not null && !string.IsNullOrEmpty(color.Val)) { fontProps = fontProps with { Color = color.Val }; } var verticalAlignment = runProperties.GetFirstChild(); if (verticalAlignment is not null && verticalAlignment.Val is not null) { fontProps = fontProps with { SubSup = verticalAlignment.Val.Value }; } return fontProps; } private static string GetRunText(Run run) { if (run is null) return string.Empty; var sb = new StringBuilder(); foreach (var text in run.Elements()) { sb.Append(text.Text ?? string.Empty); } return sb.ToString(); } }