Files

307 lines
10 KiB
C#
Raw Permalink Normal View History

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);
}
}