namespace QWERTYkez.WordProcessor; /// /// Обработчик текста в таблицах DOCX документов /// internal static class TableTextProcessor { /// /// Заменяет текст во всех таблицах указанного элемента /// 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); } } /// /// Заменяет текст во всех таблицах указанного элемента /// internal static void ReplaceInTables(OpenXmlElement element, string oldValue, IEnumerable 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); } } /// /// Выполняет словарную замену во всех таблицах /// internal static void ReplaceInTables(OpenXmlElement element, IEnumerable>> 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); } } /// /// Заменяет текст в конкретной таблице /// 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); } } /// /// Заменяет текст в конкретной таблице /// private static void ReplaceInTable(Table table, string oldValue, IEnumerable 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); } } } } /// /// Выполняет словарную замену в конкретной таблице /// private static void ReplaceInTable(Table table, IEnumerable>> 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); } } } /// /// Находит и возвращает все таблицы в указанном элементе /// internal static IEnumerable GetAllTables(OpenXmlElement element) { if (element is null) yield break; // Используем стек вместо очереди для рекурсивного поиска var stack = new Stack(); 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]); } } } /// /// Находит все параграфы внутри таблицы /// internal static IEnumerable GetTableParagraphs(Table table) { if (table is null) yield break; // Используем обход в ширину для таблиц var cells = new Queue(); foreach (var row in table.Elements()) { foreach (var cell in row.Elements()) { cells.Enqueue(cell); } } while (cells.Count > 0) { var cell = cells.Dequeue(); foreach (var paragraph in cell.Elements()) { yield return paragraph; } // Ищем вложенные таблицы в ячейке foreach (var nestedTable in cell.Elements
()) { foreach (var nestedPara in GetTableParagraphs(nestedTable)) { yield return nestedPara; } } } } /// /// Заменяет параграф в ячейке таблицы на новые параграфы /// private static void ReplaceParagraphsInTableCell(Paragraph oldParagraph, List newParagraphs) { ParagraphReplacer.ReplaceParagraph(oldParagraph, newParagraphs); } }