Files
QWERTYkez.OpenXmlProcessors/QWERTYkez.WordProcessor/TableTextProcessor.cs
2026-06-05 15:58:03 +07:00

307 lines
10 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.
namespace QWERTYkez.WordProcessor;
/// <summary>
/// Обработчик текста в таблицах DOCX документов
/// </summary>
internal static class TableTextProcessor
{
/// <summary>
/// Заменяет текст во всех таблицах указанного элемента
/// </summary>
internal static void ReplaceInTables(OpenXmlElement element, string oldValue, string newValue, StringComparison comparisonType)
{
if (element is null || string.IsNullOrEmpty(oldValue) || newValue is null)
return;
var tables = GetAllTables(element).ToList();
if (tables.Count == 0)
return;
#if DEBUG
int totalTableMatches = 0;
int totalTableParagraphs = 0;
foreach (var table in tables)
{
var paragraphs = GetTableParagraphs(table).ToList();
totalTableParagraphs += paragraphs.Count;
foreach (var para in paragraphs)
{
if (para?.InnerText?.IndexOf(oldValue, comparisonType) >= 0)
{
totalTableMatches++;
break;
}
}
}
if (totalTableMatches > 0)
{
Debug.WriteLine($"[DEBUG] [TableTextProcessor.ReplaceInTables] Looking for '{oldValue}' in {tables.Count} tables");
Debug.WriteLine($"[DEBUG] [TableTextProcessor.ReplaceInTables] Total paragraphs in tables: {totalTableParagraphs}");
Debug.WriteLine($"[DEBUG] [TableTextProcessor.ReplaceInTables] Tables with matches: {totalTableMatches}");
}
#endif
foreach (var table in tables)
{
ReplaceInTable(table, oldValue, newValue, comparisonType);
}
}
/// <summary>
/// Заменяет текст во всех таблицах указанного элемента
/// </summary>
internal static void ReplaceInTables(OpenXmlElement element, string oldValue, IEnumerable<string> newValues, StringComparison comparisonType)
{
if (element is null || string.IsNullOrEmpty(oldValue) || newValues is null)
return;
var tables = GetAllTables(element).ToList();
if (tables.Count == 0)
return;
#if DEBUG
int totalTableMatches = 0;
int totalTableParagraphs = 0;
foreach (var table in tables)
{
var paragraphs = GetTableParagraphs(table).ToList();
totalTableParagraphs += paragraphs.Count;
foreach (var para in paragraphs)
{
if (para?.InnerText?.IndexOf(oldValue, comparisonType) >= 0)
{
totalTableMatches++;
break;
}
}
}
if (totalTableMatches > 0)
{
Debug.WriteLine($"[DEBUG] [TableTextProcessor.ReplaceInTables] Looking for '{oldValue}' in {tables.Count} tables");
Debug.WriteLine($"[DEBUG] [TableTextProcessor.ReplaceInTables] Total paragraphs in tables: {totalTableParagraphs}");
Debug.WriteLine($"[DEBUG] [TableTextProcessor.ReplaceInTables] Tables with matches: {totalTableMatches}");
}
#endif
foreach (var table in tables)
{
ReplaceInTable(table, oldValue, newValues, comparisonType);
}
}
/// <summary>
/// Выполняет словарную замену во всех таблицах
/// </summary>
internal static void ReplaceInTables(OpenXmlElement element, IEnumerable<KeyValuePair<string, IEnumerable<string>>> replacements, StringComparison comparisonType)
{
if (element is null || replacements is null || !replacements.Any())
return;
var tables = GetAllTables(element).ToList();
if (tables.Count == 0)
return;
#if DEBUG
int tablesWithMatches = 0;
foreach (var table in tables)
{
var paragraphs = GetTableParagraphs(table).ToList();
foreach (var para in paragraphs)
{
var paraText = para.InnerText;
foreach (var kvp in replacements)
{
if (!string.IsNullOrEmpty(kvp.Key) && paraText.IndexOf(kvp.Key, comparisonType) >= 0)
{
tablesWithMatches++;
break;
}
}
if (tablesWithMatches > 0) break;
}
}
if (tablesWithMatches > 0)
{
Debug.WriteLine($"[DEBUG] [TableTextProcessor.ReplaceInTables(dict)] START with {replacements.Count()} items");
Debug.WriteLine($"[DEBUG] [TableTextProcessor.ReplaceInTables(dict)] Processing {tables.Count} tables, {tablesWithMatches} have matches");
}
#endif
foreach (var table in tables)
{
ReplaceInTable(table, replacements, comparisonType);
}
}
/// <summary>
/// Заменяет текст в конкретной таблице
/// </summary>
private static void ReplaceInTable(Table table, string oldValue, string newValue, StringComparison comparisonType)
{
if (table is null)
return;
var paragraphs = GetTableParagraphs(table).ToList();
if (paragraphs.Count == 0)
return;
foreach (var paragraph in paragraphs)
{
paragraph?.SimpleReplace(oldValue, newValue, comparisonType);
}
}
/// <summary>
/// Заменяет текст в конкретной таблице
/// </summary>
private static void ReplaceInTable(Table table, string oldValue, IEnumerable<string> newValues, StringComparison comparisonType)
{
if (table is null)
return;
var paragraphs = GetTableParagraphs(table).ToList();
if (paragraphs.Count == 0)
return;
if (newValues.Count() == 1)
{
foreach (var paragraph in paragraphs)
{
paragraph?.SimpleReplace(oldValue, newValues.First(), comparisonType);
}
}
else
{
for (int i = paragraphs.Count - 1; i >= 0; i--)
{
var paragraph = paragraphs[i];
if (paragraph is not null && paragraph.InnerText.IndexOf(oldValue, comparisonType) >= 0)
{
paragraph.ReplaceWithMultiple(oldValue, newValues, comparisonType);
}
}
}
}
/// <summary>
/// Выполняет словарную замену в конкретной таблице
/// </summary>
private static void ReplaceInTable(Table table, IEnumerable<KeyValuePair<string, IEnumerable<string>>> replacements, StringComparison comparisonType)
{
if (table is null || replacements is null || !replacements.Any())
return;
var paragraphs = GetTableParagraphs(table).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;
bool hasMatch = false;
foreach (var kvp in replacements)
{
if (!string.IsNullOrEmpty(kvp.Key) && paraText.IndexOf(kvp.Key, comparisonType) >= 0)
{
hasMatch = true;
break;
}
}
if (!hasMatch)
continue;
var newParagraphs = MultiReplaceExt.ProcessParagraphWithAllReplacements(paragraph, replacements, comparisonType);
if (newParagraphs is not null && newParagraphs.Count > 0)
{
ReplaceParagraphsInTableCell(paragraph, newParagraphs);
}
}
}
/// <summary>
/// Находит и возвращает все таблицы в указанном элементе
/// </summary>
internal static IEnumerable<Table> GetAllTables(OpenXmlElement element)
{
if (element is null)
yield break;
// Используем стек вместо очереди для рекурсивного поиска
var stack = new Stack<OpenXmlElement>();
stack.Push(element);
while (stack.Count > 0)
{
var current = stack.Pop();
if (current is Table table)
{
yield return table;
// Не ищем таблицы внутри таблиц (вложенные таблицы уже будут обработаны как дочерние элементы)
continue;
}
// Добавляем дочерние элементы в обратном порядке для сохранения порядка
var children = current.ChildElements;
for (int i = children.Count - 1; i >= 0; i--)
{
stack.Push(children[i]);
}
}
}
/// <summary>
/// Находит все параграфы внутри таблицы
/// </summary>
internal static IEnumerable<Paragraph> GetTableParagraphs(Table table)
{
if (table is null)
yield break;
// Используем обход в ширину для таблиц
var cells = new Queue<TableCell>();
foreach (var row in table.Elements<TableRow>())
{
foreach (var cell in row.Elements<TableCell>())
{
cells.Enqueue(cell);
}
}
while (cells.Count > 0)
{
var cell = cells.Dequeue();
foreach (var paragraph in cell.Elements<Paragraph>())
{
yield return paragraph;
}
// Ищем вложенные таблицы в ячейке
foreach (var nestedTable in cell.Elements<Table>())
{
foreach (var nestedPara in GetTableParagraphs(nestedTable))
{
yield return nestedPara;
}
}
}
}
/// <summary>
/// Заменяет параграф в ячейке таблицы на новые параграфы
/// </summary>
private static void ReplaceParagraphsInTableCell(Paragraph oldParagraph, List<Paragraph> newParagraphs)
{
ParagraphReplacer.ReplaceParagraph(oldParagraph, newParagraphs);
}
}