231 lines
8.1 KiB
C#
231 lines
8.1 KiB
C#
|
|
using QWERTYkez.WordProcessor.Builders;
|
|||
|
|
|
|||
|
|
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();
|
|||
|
|
}
|
|||
|
|
}
|