Files
QWERTYkez.OpenXmlProcessors/QWERTYkez.WordProcessor/ReplaceToTextExt.cs
melekhin f5eb667973 0.9.1
2026-06-08 14:31:31 +07:00

231 lines
8.1 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using QWERTYkez.WordProcessor;
namespace QWERTYkez.WordProcessor;
/// <summary>
/// Вспомогательный класс для замены параграфов, содержащих указанный текст, на текст, построенный с помощью TextBuilder.
/// </summary>
internal static class ReplaceToTextExt
{
/// <summary>
/// Заменяет все параграфы, содержащие указанный текст, на текст, построенный с помощью TextBuilder.
/// </summary>
internal static void ReplaceParagraphsContainingTextToText(
Body body,
string oldValue,
Action<IText> buildText)
{
if (body is null || string.IsNullOrEmpty(oldValue) || buildText is null)
return;
var paragraphs = body.Elements<Paragraph>().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) ||
paraText.IndexOf(oldValue, StringComparison.OrdinalIgnoreCase) < 0)
continue;
// Извлекаем свойства шрифта для передачи в TextBuilder
var fontProps = ExtractFontPropsFromParagraph(paragraph, oldValue);
// Извлекаем свойства параграфа для наследования
var paragraphProps = paragraph.ParagraphProperties?.CloneNode(true) as ParagraphProperties;
// Заменяем параграф на текст с наследованием форматирования
ReplaceWithTextBuilder(paragraph, buildText, fontProps, paragraphProps);
}
}
private static void ReplaceWithTextBuilder(
Paragraph paragraph,
Action<IText> buildText,
FontProps? baseFont,
ParagraphProperties? baseParagraphProps)
{
if (paragraph is null || paragraph.Parent is null || buildText is null)
return;
var parent = paragraph.Parent;
// Создаем TextBuilder с базовым шрифтом и свойствами параграфа
var builder = TextBuilder.Create(baseFont, baseParagraphProps);
buildText(builder);
var newParagraphs = 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);
for (int i = 0; i < newParagraphs.Count; i++)
{
parent.InsertAt(newParagraphs[i], paraIndex + i);
}
}
/// <summary>
/// Извлекает свойства шрифта (FontProps) из Run, содержащего указанный текст в параграфе.
/// </summary>
private static FontProps? ExtractFontPropsFromParagraph(Paragraph paragraph, string searchText)
{
if (paragraph is null || string.IsNullOrEmpty(searchText))
return null;
var runs = paragraph.Descendants<Run>().ToList();
if (runs.Count == 0)
return null;
// Собираем полный текст параграфа и позиции каждого Run для анализа
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;
}
}
// Ищем Run, который содержит искомый текст (регистронезависимо)
foreach (var (run, start, end, text) in runInfos)
{
// Проверяем, содержится ли искомый текст в этом Run
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)
{
// Находим первый Run, который пересекается с найденным текстом
var intersectingRun = runInfos.FirstOrDefault(r =>
r.Start <= textIndex && r.End > textIndex);
if (intersectingRun.Run is not null)
{
return CreateFontPropsFromRun(intersectingRun.Run);
}
}
return null; // Не удалось найти подходящий Run
}
/// <summary>
/// Создаёт FontProps на основе свойств указанного Run.
/// </summary>
private static FontProps CreateFontPropsFromRun(Run run)
{
var runProperties = run.RunProperties;
if (runProperties is null)
return new FontProps();
var fontProps = new FontProps();
// Font Family
var runFonts = runProperties.GetFirstChild<RunFonts>();
if (runFonts is not null)
{
fontProps = fontProps with
{
FontFamily = runFonts.Ascii ?? runFonts.HighAnsi ?? runFonts.ComplexScript
};
}
// Font Size
var fontSize = runProperties.GetFirstChild<FontSize>();
if (fontSize is not null && !string.IsNullOrEmpty(fontSize.Val) &&
uint.TryParse(fontSize.Val, out uint halfPoints))
{
fontProps = fontProps with { Size = halfPoints / 2.0 };
}
// Bold
var bold = runProperties.GetFirstChild<Bold>();
if (bold is not null)
{
bool isBold = true;
if (bold.Val is not null)
{
isBold = bold.Val.Value;
}
fontProps = fontProps with { IsBold = isBold };
}
// Italic
var italic = runProperties.GetFirstChild<Italic>();
if (italic is not null)
{
bool isItalic = true;
if (italic.Val is not null)
{
isItalic = italic.Val.Value;
}
fontProps = fontProps with { IsItalic = isItalic };
}
// Underline
var underline = runProperties.GetFirstChild<Underline>();
if (underline is not null && underline.Val is not null)
{
fontProps = fontProps with { Underline = underline.Val.Value };
}
// Color
var color = runProperties.GetFirstChild<Color>();
if (color is not null && !string.IsNullOrEmpty(color.Val))
{
fontProps = fontProps with { Color = color.Val };
}
// Subscript/Superscript
var verticalAlignment = runProperties.GetFirstChild<VerticalTextAlignment>();
if (verticalAlignment is not null && verticalAlignment.Val is not null)
{
fontProps = fontProps with { SubSup = verticalAlignment.Val.Value };
}
return fontProps;
}
/// <summary>
/// Вспомогательный метод для получения текста из Run.
/// </summary>
private static string GetRunText(Run run)
{
if (run is null) return string.Empty;
var sb = new StringBuilder();
foreach (var text in run.Elements<Text>())
{
sb.Append(text.Text ?? string.Empty);
}
return sb.ToString();
}
}