2026-09-08 14:03:52 +07:00
|
|
|
|
namespace QWERTYkez.WordProcessor;
|
2026-06-05 15:58:03 +07:00
|
|
|
|
|
|
|
|
|
|
internal static class ReplaceToTableExt
|
|
|
|
|
|
{
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Заменяет параграф на таблицу, созданную с помощью TableBuilder
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
internal static void ReplaceWithTableBuilder(Paragraph paragraph, Action<TableBuilder> buildTable, FontProps? baseFont)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (paragraph is null || paragraph.Parent is null || buildTable is null)
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
var parent = paragraph.Parent;
|
|
|
|
|
|
|
2026-09-24 14:09:37 +07:00
|
|
|
|
// 1. Строим таблицу
|
2026-06-05 15:58:03 +07:00
|
|
|
|
var builder = TableBuilder.Create(baseFont);
|
|
|
|
|
|
buildTable(builder);
|
|
|
|
|
|
var table = builder.Build();
|
|
|
|
|
|
|
2026-09-24 14:09:37 +07:00
|
|
|
|
// 2. Находим индекс параграфа
|
2026-06-05 15:58:03 +07:00
|
|
|
|
int paraIndex = -1;
|
|
|
|
|
|
var children = parent.ChildElements;
|
|
|
|
|
|
for (int i = 0; i < children.Count; i++)
|
|
|
|
|
|
{
|
2026-09-24 14:09:37 +07:00
|
|
|
|
if (children[i] == paragraph) { paraIndex = i; break; }
|
|
|
|
|
|
}
|
|
|
|
|
|
if (paraIndex == -1) return;
|
|
|
|
|
|
|
|
|
|
|
|
// 3. PageBreakBefore должен сработать ДО таблицы, поэтому снимаем
|
|
|
|
|
|
// его с параграфа и выносим в отдельный пустой параграф-маркер.
|
|
|
|
|
|
bool hasPageBreakBefore = false;
|
|
|
|
|
|
if (paragraph.ParagraphProperties?.GetFirstChild<PageBreakBefore>() is { } pbb)
|
|
|
|
|
|
{
|
|
|
|
|
|
hasPageBreakBefore = true;
|
|
|
|
|
|
pbb.Remove();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int insertAt = paraIndex;
|
|
|
|
|
|
if (hasPageBreakBefore)
|
|
|
|
|
|
{
|
|
|
|
|
|
var pbbPara = new Paragraph(new ParagraphProperties(new PageBreakBefore()));
|
|
|
|
|
|
parent.InsertAt(pbbPara, insertAt);
|
|
|
|
|
|
insertAt++;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 4. Вставляем таблицу — параграф при этом сдвигается на +1.
|
|
|
|
|
|
// Ссылка на paragraph остаётся валидной.
|
|
|
|
|
|
parent.InsertAt(table, insertAt);
|
|
|
|
|
|
|
|
|
|
|
|
// 5. Чистим содержимое параграфа: pPr остаётся, текст — удаляется,
|
|
|
|
|
|
// run-ы с <w:br/> — сохраняются (с отрезанным текстом).
|
|
|
|
|
|
ClearParagraphContent(paragraph);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
// Очистка содержимого параграфа с сохранением «полезных» элементов.
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
private static void ClearParagraphContent(Paragraph paragraph)
|
|
|
|
|
|
{
|
|
|
|
|
|
var toRemove = new List<OpenXmlElement>();
|
|
|
|
|
|
foreach (var child in paragraph.ChildElements)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (child is ParagraphProperties) continue;
|
|
|
|
|
|
toRemove.Add(child);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var child in toRemove)
|
|
|
|
|
|
{
|
|
|
|
|
|
// Run, содержащий page/column break — оставляем, но убираем из него весь текст.
|
|
|
|
|
|
if (child is Run run && run.Elements<Break>().Any())
|
2026-06-05 15:58:03 +07:00
|
|
|
|
{
|
2026-09-24 14:09:37 +07:00
|
|
|
|
foreach (var t in run.Elements<Text>().ToList()) t.Remove();
|
|
|
|
|
|
foreach (var other in run.ChildElements.ToList())
|
|
|
|
|
|
{
|
|
|
|
|
|
if (other is Break || other is RunProperties) continue;
|
|
|
|
|
|
other.Remove();
|
|
|
|
|
|
}
|
|
|
|
|
|
continue;
|
2026-06-05 15:58:03 +07:00
|
|
|
|
}
|
2026-09-24 14:09:37 +07:00
|
|
|
|
child.Remove();
|
2026-06-05 15:58:03 +07:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-24 14:09:37 +07:00
|
|
|
|
// Если pPr стал пустым — тоже удаляем, чтобы не оставлять мусор.
|
|
|
|
|
|
if (paragraph.ParagraphProperties is { } ppr && !ppr.ChildElements.Any())
|
|
|
|
|
|
ppr.Remove();
|
2026-06-05 15:58:03 +07:00
|
|
|
|
|
2026-09-24 14:09:37 +07:00
|
|
|
|
// Если параграф теперь полностью пуст (нет ни Runs, ни pPr) — убираем его.
|
|
|
|
|
|
if (!paragraph.ChildElements.Any())
|
|
|
|
|
|
paragraph.Remove();
|
2026-06-05 15:58:03 +07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Заменяет все параграфы, содержащие указанный текст, на таблицы
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
internal static void ReplaceParagraphsContainingTextToTable(Body body,
|
2026-09-01 15:45:39 +07:00
|
|
|
|
string oldValue, Action<TableBuilder> buildTable, FontProps? baseFont = null)
|
2026-06-05 15:58:03 +07:00
|
|
|
|
{
|
|
|
|
|
|
if (body is null || string.IsNullOrEmpty(oldValue) || buildTable 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;
|
|
|
|
|
|
|
|
|
|
|
|
// Извлекаем свойства шрифта для передачи в TableBuilder
|
2026-09-24 14:09:37 +07:00
|
|
|
|
var fontProps = paragraph.ExtractFontProps(oldValue);
|
2026-09-01 16:23:37 +07:00
|
|
|
|
|
|
|
|
|
|
if (fontProps is { } props)
|
2026-09-02 16:42:34 +07:00
|
|
|
|
fontProps = props.Override(baseFont);
|
2026-06-05 15:58:03 +07:00
|
|
|
|
|
|
|
|
|
|
// Заменяем параграф на таблицу
|
2026-09-01 16:23:37 +07:00
|
|
|
|
ReplaceWithTableBuilder(paragraph, buildTable, fontProps);
|
2026-06-05 15:58:03 +07:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|