307 lines
10 KiB
C#
307 lines
10 KiB
C#
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);
|
||
}
|
||
} |