множественные замены с параметром

This commit is contained in:
melekhin
2026-09-10 15:53:51 +07:00
parent eb3055a2e8
commit 70a19ad738
3 changed files with 621 additions and 25 deletions
@@ -0,0 +1,220 @@
namespace QWERTYkez.WordProcessor;
/// <summary>
/// Множественные динамические замены с извлечением параметра из "$key|param$".
/// Переиспользует движок <see cref="MultiReplaceExt"/> (форматирование Run-ов,
/// разрывы страниц, смена ориентации, разбиение на параграфы — всё как в оригинале).
/// </summary>
internal static class DynamicMultiReplaceExt
{
// ============================================================
// ReplaceString (Func<string, IEnumerable<string>>)
// ============================================================
internal static void ReplaceStringMultiInBody(
Body body,
string oldValue,
Func<string, IEnumerable<string>> factory,
StringComparison comparisonType)
{
if (body is null || factory is null) return;
DynamicReplaceExt.ParsePattern(oldValue, out var prefix, out var suffix);
var paragraphs = body.Elements<Paragraph>().ToList();
for (int i = paragraphs.Count - 1; i >= 0; i--)
ProcessDynamicStringMulti(paragraphs[i], prefix, suffix, factory, comparisonType);
}
internal static void ReplaceStringMultiInTables(
OpenXmlElement element,
string oldValue,
Func<string, IEnumerable<string>> factory,
StringComparison comparisonType)
{
if (element is null || factory is null) return;
DynamicReplaceExt.ParsePattern(oldValue, out var prefix, out var suffix);
foreach (var table in TableTextProcessor.GetAllTables(element))
{
var paragraphs = TableTextProcessor.GetTableParagraphs(table).ToList();
for (int i = paragraphs.Count - 1; i >= 0; i--)
ProcessDynamicStringMulti(paragraphs[i], prefix, suffix, factory, comparisonType);
}
}
internal static void ReplaceStringMultiInHeadersFooters(
WordprocessingDocument doc,
string oldValue,
Func<string, IEnumerable<string>> factory,
StringComparison comparisonType)
{
if (doc is null || factory is null) return;
DynamicReplaceExt.ParsePattern(oldValue, out var prefix, out var suffix);
foreach (var header in HeaderFooterProcessor.GetAllHeaders(doc))
ProcessStringMultiInElement(header, prefix, suffix, factory, comparisonType);
foreach (var footer in HeaderFooterProcessor.GetAllFooters(doc))
ProcessStringMultiInElement(footer, prefix, suffix, factory, comparisonType);
}
private static void ProcessStringMultiInElement(
OpenXmlElement element,
string prefix,
string suffix,
Func<string, IEnumerable<string>> factory,
StringComparison comparisonType)
{
var paragraphs = element.Descendants<Paragraph>().ToList();
var tables = TableTextProcessor.GetAllTables(element).ToList();
var tableParagraphs = tables.SelectMany(t => TableTextProcessor.GetTableParagraphs(t)).ToList();
var allParagraphs = paragraphs.Concat(tableParagraphs).Distinct().ToList();
for (int i = allParagraphs.Count - 1; i >= 0; i--)
ProcessDynamicStringMulti(allParagraphs[i], prefix, suffix, factory, comparisonType);
}
private static void ProcessDynamicStringMulti(
Paragraph paragraph,
string prefix,
string suffix,
Func<string, IEnumerable<string>> factory,
StringComparison comparisonType)
{
if (paragraph is null || paragraph.Parent is null) return;
var text = paragraph.InnerText;
if (string.IsNullOrEmpty(text)) return;
var matches = DynamicReplaceExt.FindMatches(text, prefix, suffix, comparisonType);
if (matches.Count == 0) return;
// Строим словарь уникальных matchedText -> значения для этого параграфа
var dict = new Dictionary<string, IEnumerable<string>>();
for (int i = 0; i < matches.Count; i++)
{
var m = matches[i];
string matchedText = text.Substring(m.Start, m.End - m.Start);
if (dict.ContainsKey(matchedText)) continue;
IEnumerable<string> values;
try { values = factory(m.Param); }
catch { continue; }
if (values is null) continue;
var list = values as IList<string> ?? values.ToList();
if (list.Count == 0) continue;
dict[matchedText] = list;
}
if (dict.Count == 0) return;
var newParas = MultiReplaceExt.ProcessParagraphWithMergedBoundaries(paragraph, dict, comparisonType);
if (newParas is not null && newParas.Count > 0)
ParagraphReplacer.ReplaceParagraph(paragraph, newParas);
}
// ============================================================
// ReplaceItem (Func<string, IEnumerable<ReplaceItem>>)
// ============================================================
internal static void ReplaceItemMultiInBody(
Body body,
string oldValue,
Func<string, IEnumerable<ReplaceItem>> factory,
StringComparison comparisonType)
{
if (body is null || factory is null) return;
DynamicReplaceExt.ParsePattern(oldValue, out var prefix, out var suffix);
var paragraphs = body.Elements<Paragraph>().ToList();
for (int i = paragraphs.Count - 1; i >= 0; i--)
ProcessDynamicItemMulti(paragraphs[i], prefix, suffix, factory, comparisonType);
}
internal static void ReplaceItemMultiInTables(
OpenXmlElement element,
string oldValue,
Func<string, IEnumerable<ReplaceItem>> factory,
StringComparison comparisonType)
{
if (element is null || factory is null) return;
DynamicReplaceExt.ParsePattern(oldValue, out var prefix, out var suffix);
foreach (var table in TableTextProcessor.GetAllTables(element))
{
var paragraphs = TableTextProcessor.GetTableParagraphs(table).ToList();
for (int i = paragraphs.Count - 1; i >= 0; i--)
ProcessDynamicItemMulti(paragraphs[i], prefix, suffix, factory, comparisonType);
}
}
internal static void ReplaceItemMultiInHeadersFooters(
WordprocessingDocument doc,
string oldValue,
Func<string, IEnumerable<ReplaceItem>> factory,
StringComparison comparisonType)
{
if (doc is null || factory is null) return;
DynamicReplaceExt.ParsePattern(oldValue, out var prefix, out var suffix);
foreach (var header in HeaderFooterProcessor.GetAllHeaders(doc))
ProcessItemMultiInElement(header, prefix, suffix, factory, comparisonType);
foreach (var footer in HeaderFooterProcessor.GetAllFooters(doc))
ProcessItemMultiInElement(footer, prefix, suffix, factory, comparisonType);
}
private static void ProcessItemMultiInElement(
OpenXmlElement element,
string prefix,
string suffix,
Func<string, IEnumerable<ReplaceItem>> factory,
StringComparison comparisonType)
{
var paragraphs = element.Descendants<Paragraph>().ToList();
var tables = TableTextProcessor.GetAllTables(element).ToList();
var tableParagraphs = tables.SelectMany(t => TableTextProcessor.GetTableParagraphs(t)).ToList();
var allParagraphs = paragraphs.Concat(tableParagraphs).Distinct().ToList();
for (int i = allParagraphs.Count - 1; i >= 0; i--)
ProcessDynamicItemMulti(allParagraphs[i], prefix, suffix, factory, comparisonType);
}
private static void ProcessDynamicItemMulti(
Paragraph paragraph,
string prefix,
string suffix,
Func<string, IEnumerable<ReplaceItem>> factory,
StringComparison comparisonType)
{
if (paragraph is null || paragraph.Parent is null) return;
var text = paragraph.InnerText;
if (string.IsNullOrEmpty(text)) return;
var matches = DynamicReplaceExt.FindMatches(text, prefix, suffix, comparisonType);
if (matches.Count == 0) return;
var dict = new Dictionary<string, IEnumerable<ReplaceItem>>();
for (int i = 0; i < matches.Count; i++)
{
var m = matches[i];
string matchedText = text.Substring(m.Start, m.End - m.Start);
if (dict.ContainsKey(matchedText)) continue;
IEnumerable<ReplaceItem> values;
try { values = factory(m.Param); }
catch { continue; }
if (values is null) continue;
var list = values as IList<ReplaceItem> ?? values.ToList();
if (list.Count == 0) continue;
dict[matchedText] = list;
}
if (dict.Count == 0) return;
var newParas = MultiReplaceExt.ProcessParagraphWithMergedBoundaries(paragraph, dict, comparisonType);
if (newParas is not null && newParas.Count > 0)
ParagraphReplacer.ReplaceParagraph(paragraph, newParas);
}
}