371 lines
13 KiB
C#
371 lines
13 KiB
C#
|
|
namespace QWERTYkez.WordProcessor;
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// Обработчик текста в верхних и нижних колонтитулах DOCX документов
|
|||
|
|
/// </summary>
|
|||
|
|
internal static class HeaderFooterProcessor
|
|||
|
|
{
|
|||
|
|
/// <summary>
|
|||
|
|
/// Заменяет текст во всех колонтитулах документа
|
|||
|
|
/// </summary>
|
|||
|
|
internal static void ReplaceInHeadersFooters(WordprocessingDocument document, string oldValue, IEnumerable<string> newValues, StringComparison comparisonType)
|
|||
|
|
{
|
|||
|
|
if (document is null || string.IsNullOrEmpty(oldValue) || newValues is null)
|
|||
|
|
return;
|
|||
|
|
|
|||
|
|
var headers = GetAllHeaders(document).ToList();
|
|||
|
|
var footers = GetAllFooters(document).ToList();
|
|||
|
|
|
|||
|
|
if (headers.Count == 0 && footers.Count == 0)
|
|||
|
|
return;
|
|||
|
|
|
|||
|
|
#if DEBUG
|
|||
|
|
int headerMatches = 0;
|
|||
|
|
int footerMatches = 0;
|
|||
|
|
|
|||
|
|
foreach (var header in headers)
|
|||
|
|
{
|
|||
|
|
if (ContainsText(header, oldValue, comparisonType))
|
|||
|
|
{
|
|||
|
|
headerMatches++;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
foreach (var footer in footers)
|
|||
|
|
{
|
|||
|
|
if (ContainsText(footer, oldValue, comparisonType))
|
|||
|
|
{
|
|||
|
|
footerMatches++;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (headerMatches > 0 || footerMatches > 0)
|
|||
|
|
{
|
|||
|
|
Debug.WriteLine($"[DEBUG] [HeaderFooterProcessor.ReplaceInHeadersFooters] Looking for '{oldValue}'");
|
|||
|
|
Debug.WriteLine($"[DEBUG] [HeaderFooterProcessor.ReplaceInHeadersFooters] Headers: {headers.Count} total, {headerMatches} with matches");
|
|||
|
|
Debug.WriteLine($"[DEBUG] [HeaderFooterProcessor.ReplaceInHeadersFooters] Footers: {footers.Count} total, {footerMatches} with matches");
|
|||
|
|
}
|
|||
|
|
#endif
|
|||
|
|
|
|||
|
|
foreach (var header in headers)
|
|||
|
|
{
|
|||
|
|
ReplaceInElement(header, oldValue, newValues, comparisonType);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
foreach (var footer in footers)
|
|||
|
|
{
|
|||
|
|
ReplaceInElement(footer, oldValue, newValues, comparisonType);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// Выполняет словарную замену во всех колонтитулах
|
|||
|
|
/// </summary>
|
|||
|
|
internal static void ReplaceInHeadersFooters(WordprocessingDocument document, IEnumerable<KeyValuePair<string, IEnumerable<string>>> replacements, StringComparison comparisonType)
|
|||
|
|
{
|
|||
|
|
if (document is null || replacements is null || !replacements.Any())
|
|||
|
|
return;
|
|||
|
|
|
|||
|
|
var headers = GetAllHeaders(document).ToList();
|
|||
|
|
var footers = GetAllFooters(document).ToList();
|
|||
|
|
|
|||
|
|
if (headers.Count == 0 && footers.Count == 0)
|
|||
|
|
return;
|
|||
|
|
|
|||
|
|
#if DEBUG
|
|||
|
|
int headerMatches = 0;
|
|||
|
|
int footerMatches = 0;
|
|||
|
|
|
|||
|
|
foreach (var header in headers)
|
|||
|
|
{
|
|||
|
|
foreach (var kvp in replacements)
|
|||
|
|
{
|
|||
|
|
if (!string.IsNullOrEmpty(kvp.Key) && ContainsText(header, kvp.Key, comparisonType))
|
|||
|
|
{
|
|||
|
|
headerMatches++;
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
foreach (var footer in footers)
|
|||
|
|
{
|
|||
|
|
foreach (var kvp in replacements)
|
|||
|
|
{
|
|||
|
|
if (!string.IsNullOrEmpty(kvp.Key) && ContainsText(footer, kvp.Key, comparisonType))
|
|||
|
|
{
|
|||
|
|
footerMatches++;
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (headerMatches > 0 || footerMatches > 0)
|
|||
|
|
{
|
|||
|
|
Debug.WriteLine($"[DEBUG] [HeaderFooterProcessor.ReplaceInHeadersFooters(dict)] START with {replacements.Count()} items");
|
|||
|
|
Debug.WriteLine($"[DEBUG] [HeaderFooterProcessor.ReplaceInHeadersFooters(dict)] Headers: {headers.Count} total, {headerMatches} with matches");
|
|||
|
|
Debug.WriteLine($"[DEBUG] [HeaderFooterProcessor.ReplaceInHeadersFooters(dict)] Footers: {footers.Count} total, {footerMatches} with matches");
|
|||
|
|
}
|
|||
|
|
#endif
|
|||
|
|
|
|||
|
|
foreach (var header in headers)
|
|||
|
|
{
|
|||
|
|
ReplaceInElement(header, replacements, comparisonType);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
foreach (var footer in footers)
|
|||
|
|
{
|
|||
|
|
ReplaceInElement(footer, replacements, comparisonType);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// Выполняет словарную замену во всех колонтитулах
|
|||
|
|
/// </summary>
|
|||
|
|
internal static void ReplaceInHeadersFooters(WordprocessingDocument document, IEnumerable<KeyValuePair<string, string>> replacements, StringComparison comparisonType)
|
|||
|
|
{
|
|||
|
|
if (document is null || replacements is null || !replacements.Any())
|
|||
|
|
return;
|
|||
|
|
|
|||
|
|
var headers = GetAllHeaders(document).ToList();
|
|||
|
|
var footers = GetAllFooters(document).ToList();
|
|||
|
|
|
|||
|
|
if (headers.Count == 0 && footers.Count == 0)
|
|||
|
|
return;
|
|||
|
|
|
|||
|
|
#if DEBUG
|
|||
|
|
int headerMatches = 0;
|
|||
|
|
int footerMatches = 0;
|
|||
|
|
|
|||
|
|
foreach (var header in headers)
|
|||
|
|
{
|
|||
|
|
foreach (var kvp in replacements)
|
|||
|
|
{
|
|||
|
|
if (!string.IsNullOrEmpty(kvp.Key) && ContainsText(header, kvp.Key, comparisonType))
|
|||
|
|
{
|
|||
|
|
headerMatches++;
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
foreach (var footer in footers)
|
|||
|
|
{
|
|||
|
|
foreach (var kvp in replacements)
|
|||
|
|
{
|
|||
|
|
if (!string.IsNullOrEmpty(kvp.Key) && ContainsText(footer, kvp.Key, comparisonType))
|
|||
|
|
{
|
|||
|
|
footerMatches++;
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (headerMatches > 0 || footerMatches > 0)
|
|||
|
|
{
|
|||
|
|
Debug.WriteLine($"[DEBUG] [HeaderFooterProcessor.ReplaceInHeadersFooters(dict)] START with {replacements.Count()} items");
|
|||
|
|
Debug.WriteLine($"[DEBUG] [HeaderFooterProcessor.ReplaceInHeadersFooters(dict)] Headers: {headers.Count} total, {headerMatches} with matches");
|
|||
|
|
Debug.WriteLine($"[DEBUG] [HeaderFooterProcessor.ReplaceInHeadersFooters(dict)] Footers: {footers.Count} total, {footerMatches} with matches");
|
|||
|
|
}
|
|||
|
|
#endif
|
|||
|
|
|
|||
|
|
foreach (var header in headers)
|
|||
|
|
{
|
|||
|
|
ReplaceInElement(header, replacements, comparisonType);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
foreach (var footer in footers)
|
|||
|
|
{
|
|||
|
|
ReplaceInElement(footer, replacements, comparisonType);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// Заменяет текст в указанном элементе (Header или Footer)
|
|||
|
|
/// </summary>
|
|||
|
|
private static void ReplaceInElement(OpenXmlElement element, string oldValue, IEnumerable<string> newValues, StringComparison comparisonType)
|
|||
|
|
{
|
|||
|
|
if (element is null)
|
|||
|
|
return;
|
|||
|
|
|
|||
|
|
var paragraphs = element.Descendants<Paragraph>().ToList();
|
|||
|
|
if (paragraphs.Count == 0)
|
|||
|
|
return;
|
|||
|
|
|
|||
|
|
var tables = TableTextProcessor.GetAllTables(element).ToList();
|
|||
|
|
var tableParagraphs = tables.SelectMany(t => TableTextProcessor.GetTableParagraphs(t)).ToList();
|
|||
|
|
|
|||
|
|
var allParagraphs = paragraphs.Concat(tableParagraphs).Distinct().ToList();
|
|||
|
|
|
|||
|
|
if (newValues.Count() == 1)
|
|||
|
|
{
|
|||
|
|
foreach (var paragraph in allParagraphs)
|
|||
|
|
{
|
|||
|
|
paragraph?.SimpleReplace(oldValue, newValues.First(), comparisonType);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
foreach (var paragraph in allParagraphs)
|
|||
|
|
{
|
|||
|
|
if (paragraph is not null && paragraph.InnerText.IndexOf(oldValue, comparisonType) >= 0)
|
|||
|
|
{
|
|||
|
|
paragraph.ReplaceWithMultiple(oldValue, newValues, comparisonType);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// Выполняет словарную замену в указанном элементе
|
|||
|
|
/// </summary>
|
|||
|
|
private static void ReplaceInElement(OpenXmlElement element, IEnumerable<KeyValuePair<string, IEnumerable<string>>> replacements, StringComparison comparisonType)
|
|||
|
|
{
|
|||
|
|
if (element is null || replacements is null || !replacements.Any())
|
|||
|
|
return;
|
|||
|
|
|
|||
|
|
var paragraphs = element.Descendants<Paragraph>().ToList();
|
|||
|
|
if (paragraphs.Count == 0)
|
|||
|
|
return;
|
|||
|
|
|
|||
|
|
var tables = TableTextProcessor.GetAllTables(element).ToList();
|
|||
|
|
var tableParagraphs = tables.SelectMany(t => TableTextProcessor.GetTableParagraphs(t)).ToList();
|
|||
|
|
|
|||
|
|
var allParagraphs = paragraphs.Concat(tableParagraphs).Distinct().ToList();
|
|||
|
|
|
|||
|
|
foreach (var paragraph in allParagraphs)
|
|||
|
|
{
|
|||
|
|
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)
|
|||
|
|
{
|
|||
|
|
ReplaceParagraphInParent(paragraph, newParagraphs);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// Выполняет словарную замену в указанном элементе
|
|||
|
|
/// </summary>
|
|||
|
|
private static void ReplaceInElement(OpenXmlElement element, IEnumerable<KeyValuePair<string, string>> replacements, StringComparison comparisonType)
|
|||
|
|
{
|
|||
|
|
if (element is null || replacements is null || !replacements.Any())
|
|||
|
|
return;
|
|||
|
|
|
|||
|
|
var paragraphs = element.Descendants<Paragraph>().ToList();
|
|||
|
|
if (paragraphs.Count == 0)
|
|||
|
|
return;
|
|||
|
|
|
|||
|
|
var tables = TableTextProcessor.GetAllTables(element).ToList();
|
|||
|
|
var tableParagraphs = tables.SelectMany(t => TableTextProcessor.GetTableParagraphs(t)).ToList();
|
|||
|
|
|
|||
|
|
var allParagraphs = paragraphs.Concat(tableParagraphs).Distinct().ToList();
|
|||
|
|
|
|||
|
|
foreach (var paragraph in allParagraphs)
|
|||
|
|
{
|
|||
|
|
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)
|
|||
|
|
{
|
|||
|
|
ReplaceParagraphInParent(paragraph, newParagraphs);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#if DEBUG
|
|||
|
|
private static bool ContainsText(OpenXmlElement element, string searchText, StringComparison comparisonType)
|
|||
|
|
{
|
|||
|
|
if (element is null || string.IsNullOrEmpty(searchText))
|
|||
|
|
return false;
|
|||
|
|
|
|||
|
|
return element.InnerText?.IndexOf(searchText, comparisonType) >= 0;
|
|||
|
|
}
|
|||
|
|
#endif
|
|||
|
|
|
|||
|
|
// Остальные методы остаются без изменений
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// Находит и возвращает все верхние колонтитулы в документе
|
|||
|
|
/// </summary>
|
|||
|
|
internal static IEnumerable<OpenXmlElement> GetAllHeaders(WordprocessingDocument document)
|
|||
|
|
{
|
|||
|
|
if (document?.MainDocumentPart is null)
|
|||
|
|
yield break;
|
|||
|
|
|
|||
|
|
foreach (var headerPart in document.MainDocumentPart.HeaderParts)
|
|||
|
|
{
|
|||
|
|
if (headerPart?.Header is not null)
|
|||
|
|
{
|
|||
|
|
yield return headerPart.Header;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// Находит и возвращает все нижние колонтитулы в документе
|
|||
|
|
/// </summary>
|
|||
|
|
internal static IEnumerable<OpenXmlElement> GetAllFooters(WordprocessingDocument document)
|
|||
|
|
{
|
|||
|
|
if (document?.MainDocumentPart is null)
|
|||
|
|
yield break;
|
|||
|
|
|
|||
|
|
foreach (var footerPart in document.MainDocumentPart.FooterParts)
|
|||
|
|
{
|
|||
|
|
if (footerPart?.Footer is not null)
|
|||
|
|
{
|
|||
|
|
yield return footerPart.Footer;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// Проверяет, содержит ли документ колонтитулы
|
|||
|
|
/// </summary>
|
|||
|
|
internal static bool HasHeadersOrFooters(WordprocessingDocument document)
|
|||
|
|
{
|
|||
|
|
if (document?.MainDocumentPart is null)
|
|||
|
|
return false;
|
|||
|
|
|
|||
|
|
return document.MainDocumentPart.HeaderParts.Any() || document.MainDocumentPart.FooterParts.Any();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// Заменяет параграф на новые параграфы в родительском элементе
|
|||
|
|
/// </summary>
|
|||
|
|
private static void ReplaceParagraphInParent(Paragraph oldParagraph, List<Paragraph> newParagraphs)
|
|||
|
|
{
|
|||
|
|
ParagraphReplacer.ReplaceParagraph(oldParagraph, newParagraphs);
|
|||
|
|
}
|
|||
|
|
}
|