Добавьте файлы проекта.
This commit is contained in:
307
QWERTYkez.WordProcessor/TableTextProcessor.cs
Normal file
307
QWERTYkez.WordProcessor/TableTextProcessor.cs
Normal file
@@ -0,0 +1,307 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user