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