diff --git a/QWERTYkez.WordProcessor/DynamicMultiReplaceExt.cs b/QWERTYkez.WordProcessor/DynamicMultiReplaceExt.cs
new file mode 100644
index 0000000..865a0de
--- /dev/null
+++ b/QWERTYkez.WordProcessor/DynamicMultiReplaceExt.cs
@@ -0,0 +1,220 @@
+namespace QWERTYkez.WordProcessor;
+
+///
+/// Множественные динамические замены с извлечением параметра из "$key|param$".
+/// Переиспользует движок (форматирование Run-ов,
+/// разрывы страниц, смена ориентации, разбиение на параграфы — всё как в оригинале).
+///
+internal static class DynamicMultiReplaceExt
+{
+ // ============================================================
+ // ReplaceString (Func>)
+ // ============================================================
+
+ internal static void ReplaceStringMultiInBody(
+ Body body,
+ string oldValue,
+ Func> factory,
+ StringComparison comparisonType)
+ {
+ if (body is null || factory is null) return;
+ DynamicReplaceExt.ParsePattern(oldValue, out var prefix, out var suffix);
+
+ var paragraphs = body.Elements().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> 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> 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> factory,
+ StringComparison comparisonType)
+ {
+ var paragraphs = element.Descendants().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> 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>();
+ 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 values;
+ try { values = factory(m.Param); }
+ catch { continue; }
+ if (values is null) continue;
+
+ var list = values as IList ?? 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>)
+ // ============================================================
+
+ internal static void ReplaceItemMultiInBody(
+ Body body,
+ string oldValue,
+ Func> factory,
+ StringComparison comparisonType)
+ {
+ if (body is null || factory is null) return;
+ DynamicReplaceExt.ParsePattern(oldValue, out var prefix, out var suffix);
+
+ var paragraphs = body.Elements().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> 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> 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> factory,
+ StringComparison comparisonType)
+ {
+ var paragraphs = element.Descendants().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> 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>();
+ 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 values;
+ try { values = factory(m.Param); }
+ catch { continue; }
+ if (values is null) continue;
+
+ var list = values as IList ?? 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);
+ }
+}
\ No newline at end of file
diff --git a/QWERTYkez.WordProcessor/MultiReplace.cs b/QWERTYkez.WordProcessor/MultiReplace.cs
index c1355fe..c1cb152 100644
--- a/QWERTYkez.WordProcessor/MultiReplace.cs
+++ b/QWERTYkez.WordProcessor/MultiReplace.cs
@@ -402,7 +402,7 @@ internal static class MultiReplaceExt
// ---------- ОСНОВНОЙ АЛГОРИТМ ----------
- private static List? ProcessMultiReplacements(
+ internal static List? ProcessMultiReplacements(
Paragraph original,
IEnumerable>>? stringReplacements,
IEnumerable>>? itemReplacements,
@@ -848,4 +848,352 @@ internal static class MultiReplaceExt
}
}
}
+
+
+ // ============================================================
+ // НОВЫЕ ПУБЛИЧНЫЕ ТОЧКИ ВХОДА (mergeBoundaryText: true)
+ // ============================================================
+
+ internal static List? ProcessParagraphWithMergedBoundaries(
+ Paragraph paragraph,
+ IEnumerable>> replacements,
+ StringComparison comparisonType)
+ {
+ return ProcessMultiReplacements(paragraph, replacements, null, comparisonType, mergeBoundaryText: true);
+ }
+
+ internal static List? ProcessParagraphWithMergedBoundaries(
+ Paragraph paragraph,
+ IEnumerable>> replacements,
+ StringComparison comparisonType)
+ {
+ return ProcessMultiReplacements(paragraph, null, replacements, comparisonType, mergeBoundaryText: true);
+ }
+
+ // ============================================================
+ // ОСНОВНОЙ АЛГОРИТМ (изменена только сигнатура + 4 места)
+ // ============================================================
+
+ private static List? ProcessMultiReplacements(
+ Paragraph original,
+ IEnumerable>>? stringReplacements,
+ IEnumerable>>? itemReplacements,
+ StringComparison comparisonType,
+ bool mergeBoundaryText = false) // <-- новый параметр
+ {
+ var body = original.Ancestors().FirstOrDefault();
+
+ bool hasEmptyParagraphsBefore = false;
+ var prevPara = original.PreviousSibling();
+ while (prevPara is not null)
+ {
+ if (!string.IsNullOrEmpty(prevPara.InnerText)) break;
+ hasEmptyParagraphsBefore = true;
+ prevPara = prevPara.PreviousSibling();
+ }
+
+ SectionProperties? followingSection = null;
+ if (original.ParagraphProperties?.GetFirstChild()?.CloneNode(true) is not SectionProperties sourceSection)
+ {
+ var nextPara = original.NextSibling();
+ if (nextPara is not null)
+ {
+ followingSection = nextPara.ParagraphProperties?.GetFirstChild()?.CloneNode(true) as SectionProperties;
+ }
+ }
+ else
+ {
+ followingSection = sourceSection;
+ }
+ followingSection ??= new SectionProperties();
+
+ // 1. Сбор определений
+ var definitions = new List();
+ if (stringReplacements is not null)
+ {
+ foreach (var kvp in stringReplacements)
+ {
+ if (string.IsNullOrEmpty(kvp.Key) || kvp.Value is null || kvp.Value.Count() == 0) continue;
+ var items = kvp.Value.Select(v => new ReplaceItem(v));
+ definitions.Add(new MatchDefinition(kvp.Key, items));
+ }
+ }
+ if (itemReplacements is not null)
+ {
+ foreach (var kvp in itemReplacements)
+ {
+ if (string.IsNullOrEmpty(kvp.Key) || kvp.Value is null || kvp.Value.Count() == 0) continue;
+ definitions.Add(new MatchDefinition(kvp.Key, kvp.Value));
+ }
+ }
+ if (definitions.Count == 0) return null;
+
+ // 2. Анализ структуры параграфа
+ var runs = original.Descendants().ToList();
+ if (runs.Count == 0) return null;
+ var structure = AnalyzeParagraphStructure(runs);
+ string fullText = structure.FullText;
+ if (fullText.Length == 0) return null;
+
+ // 3. Поиск всех вхождений
+ var matches = new List();
+ foreach (var def in definitions)
+ {
+ int pos = 0;
+ while ((pos = fullText.IndexOf(def.Key, pos, comparisonType)) != -1)
+ {
+ matches.Add(new Match { Definition = def, Start = pos, End = pos + def.Key.Length });
+ pos += def.Key.Length;
+ }
+ }
+ if (matches.Count == 0) return null;
+ matches.Sort((a, b) => a.Start.CompareTo(b.Start));
+
+ // 4. Книжная секция для копирования полей
+ SectionProperties? portraitSection = null;
+ if (body is not null)
+ portraitSection = body.Elements().FirstOrDefault()?.CloneNode(true) as SectionProperties;
+ portraitSection ??= new SectionProperties();
+
+ // 5. Построение результата
+ var resultParas = new List();
+ Paragraph? currentPara = null;
+ int currentPos = 0;
+ bool sectionChangeInsideGroup = false;
+ BreakType? lastOrientation = null;
+
+ bool pageBreakBeforeNext = false;
+ BreakType? pendingOrientation = null;
+ bool firstSplitMarkerHandled = false;
+ int textCount = 0;
+ Paragraph? lastTextPara = null;
+ bool emptyParagraphCreated = false;
+
+ // НОВОЕ: буфер runs для склейки "before"-текста с первым value-параграфом
+ List? pendingBeforeRuns = null;
+
+ for (int i = 0; i < matches.Count; i++)
+ {
+ var match = matches[i];
+
+ // ---- Текст перед совпадением ----
+ if (currentPos < match.Start)
+ {
+ var beforePara = CloneParagraphWithoutSection(original);
+ foreach (var seg in structure.Segments)
+ {
+ if (seg.End <= currentPos) continue;
+ if (seg.Start >= match.Start) break;
+ if (seg.Start >= currentPos && seg.End <= match.Start)
+ {
+ beforePara.AppendChild(seg.Run.CloneNode(true));
+ }
+ else if (seg.Start < match.Start && seg.End > currentPos)
+ {
+ int startOffset = Math.Max(0, currentPos - seg.Start);
+ int endOffset = Math.Min(seg.Text.Length, match.Start - seg.Start);
+ var runClone = (Run)seg.Run.CloneNode(true);
+ foreach (var t in runClone.Elements().ToList()) t.Remove();
+ string textPart = seg.Text.Substring(startOffset, endOffset - startOffset);
+ runClone.AppendChild(new Text(textPart));
+ beforePara.AppendChild(runClone);
+ }
+ }
+
+ if (beforePara.ChildElements.OfType().Any())
+ {
+ if (!mergeBoundaryText)
+ {
+ resultParas.Add(beforePara);
+ currentPara = beforePara;
+ lastTextPara = beforePara;
+ }
+ else
+ {
+ // Копим runs, склеим их с первым value-параграфом
+ pendingBeforeRuns = new List();
+ foreach (var r in beforePara.Elements())
+ pendingBeforeRuns.Add((Run)r.CloneNode(true));
+ }
+ }
+ }
+ // ---- /Текст перед совпадением ----
+
+ var values = match.Definition.Values.ToList();
+ if (values.Count == 0) continue;
+
+ // Fast-path (одиночный простой текст) — оставлен как есть.
+ // В merge-режиме пропускаем, чтобы before/remainder могли склеиться.
+ if (!mergeBoundaryText
+ && values.Count == 1
+ && !string.IsNullOrEmpty(values[0].Text)
+ && !values[0].BreakValue.HasValue)
+ {
+ var newText = values[0].Text;
+ var matchSeg = structure.Segments.FirstOrDefault(s => s.Start >= match.Start && s.End <= match.End);
+ if (matchSeg is not null)
+ {
+ var run = matchSeg.Run;
+ foreach (var textElem in run.Elements().ToList())
+ textElem.Remove();
+ run.AppendChild(new Text(newText));
+ return null;
+ }
+ }
+
+ // Сброс флагов для новой группы
+ pageBreakBeforeNext = false;
+ pendingOrientation = null;
+ firstSplitMarkerHandled = false;
+ textCount = 0;
+ lastTextPara = null;
+ emptyParagraphCreated = false;
+
+ foreach (var item in values)
+ {
+ if (!string.IsNullOrEmpty(item.Text))
+ {
+ ProcessTextItem(
+ item.Text,
+ ref pendingOrientation,
+ ref currentPara,
+ ref lastTextPara,
+ ref textCount,
+ ref pageBreakBeforeNext,
+ ref sectionChangeInsideGroup,
+ ref lastOrientation,
+ original,
+ structure,
+ match.Start,
+ followingSection,
+ portraitSection,
+ resultParas);
+
+ // НОВОЕ: вклеиваем before-runs в только что созданный параграф
+ if (mergeBoundaryText && pendingBeforeRuns is not null && currentPara is not null)
+ {
+ for (int j = pendingBeforeRuns.Count - 1; j >= 0; j--)
+ currentPara.InsertAt((Run)pendingBeforeRuns[j].CloneNode(true), 0);
+ pendingBeforeRuns = null;
+ }
+ }
+ else if (item.BreakValue.HasValue)
+ {
+ ProcessBreakItem(
+ item.BreakValue.Value,
+ ref pendingOrientation,
+ ref currentPara,
+ ref lastTextPara,
+ ref pageBreakBeforeNext,
+ ref firstSplitMarkerHandled,
+ ref sectionChangeInsideGroup,
+ ref lastOrientation,
+ ref textCount,
+ ref emptyParagraphCreated,
+ hasEmptyParagraphsBefore,
+ original,
+ followingSection,
+ portraitSection,
+ resultParas);
+
+ // НОВОЕ: если создался параграф под break (landscape/portrait), тоже вклеим
+ if (mergeBoundaryText && pendingBeforeRuns is not null && currentPara is not null)
+ {
+ // вставляем before-runs ПЕРЕД любыми break-runs
+ for (int j = pendingBeforeRuns.Count - 1; j >= 0; j--)
+ currentPara.InsertAt((Run)pendingBeforeRuns[j].CloneNode(true), 0);
+ pendingBeforeRuns = null;
+ }
+ }
+ }
+
+ // Закрытие секции (без изменений)
+ if (sectionChangeInsideGroup && lastOrientation.HasValue && currentPara is not null)
+ {
+ if (currentPara.ParagraphProperties?.GetFirstChild() is null)
+ {
+ bool addPageSize = (lastOrientation.Value == BreakType.NewLandscapeSection);
+ AddSectionProperties(currentPara, lastOrientation.Value, addPageSize, followingSection, portraitSection);
+ }
+ }
+
+ if (!sectionChangeInsideGroup && currentPara is not null)
+ {
+ var sourcePageSize = followingSection.GetFirstChild();
+ if (sourcePageSize is not null)
+ {
+ if (currentPara.ParagraphProperties?.GetFirstChild() is null)
+ {
+ currentPara.ParagraphProperties ??= new ParagraphProperties();
+ currentPara.ParagraphProperties.AppendChild(followingSection.CloneNode(true));
+ }
+ }
+ }
+
+ EnsureBodyLandscapeSection(body!, followingSection, sectionChangeInsideGroup, lastOrientation);
+
+ currentPos = match.End;
+ }
+
+ // ---- Обработка остатка текста ----
+ if (currentPos < fullText.Length)
+ {
+ var remainderPara = BuildRemainderParagraph(original, structure, currentPos);
+ if (remainderPara is not null)
+ {
+ bool canMerge = mergeBoundaryText
+ && resultParas.Count > 0
+ && !pendingOrientation.HasValue
+ && !sectionChangeInsideGroup;
+
+ if (canMerge)
+ {
+ // Вклеиваем runs остатка в последний параграф
+ var lastPara = resultParas[resultParas.Count - 1];
+ foreach (var r in remainderPara.Elements().ToList())
+ lastPara.AppendChild(r.CloneNode(true));
+ }
+ else
+ {
+ // Оригинальное поведение
+ if (pendingOrientation.HasValue)
+ {
+ var breakRun = new Run(new Break { Type = BreakValues.Page });
+ remainderPara.InsertAt(breakRun, 0);
+ bool addPageSize = (pendingOrientation.Value == BreakType.NewLandscapeSection);
+ AddSectionProperties(remainderPara, pendingOrientation.Value, addPageSize, followingSection, portraitSection);
+ pendingOrientation = null;
+ }
+ else if (sectionChangeInsideGroup && lastOrientation.HasValue)
+ {
+ var breakRun = new Run(new Break { Type = BreakValues.Page });
+ remainderPara.InsertAt(breakRun, 0);
+ bool addPageSize = (lastOrientation.Value == BreakType.NewLandscapeSection);
+ AddSectionProperties(remainderPara, lastOrientation.Value, addPageSize, followingSection, portraitSection);
+ }
+ else
+ {
+ var sourcePageSize = followingSection.GetFirstChild();
+ if (sourcePageSize is not null)
+ {
+ remainderPara.ParagraphProperties ??= new ParagraphProperties();
+ remainderPara.ParagraphProperties.AppendChild(followingSection.CloneNode(true));
+ }
+ }
+ resultParas.Add(remainderPara);
+ }
+ }
+ }
+ // ---- /Обработка остатка текста ----
+
+ // 7. Очистка пустых параграфов
+ for (int i = resultParas.Count - 1; i >= 0; i--)
+ {
+ var p = resultParas[i];
+ if (!p.ChildElements.OfType().Any() && p.ParagraphProperties is null)
+ resultParas.RemoveAt(i);
+ }
+
+ return resultParas.Count > 0 ? resultParas : null;
+ }
}
\ No newline at end of file
diff --git a/QWERTYkez.WordProcessor/WordWriter.cs b/QWERTYkez.WordProcessor/WordWriter.cs
index 305aedd..f5dbf9c 100644
--- a/QWERTYkez.WordProcessor/WordWriter.cs
+++ b/QWERTYkez.WordProcessor/WordWriter.cs
@@ -344,7 +344,7 @@ internal sealed class WordWriter : WordReader, IWordWriter
-
+ #region динамические змены с параметром
public void ReplaceString(string oldValue, Func newValues)
{
@@ -373,7 +373,6 @@ internal sealed class WordWriter : WordReader, IWordWriter
_isModified = true;
}
}
-
public void ReplaceItem(string oldValue, Func newValues)
{
ThrowIfDisposed();
@@ -401,7 +400,6 @@ internal sealed class WordWriter : WordReader, IWordWriter
_isModified = true;
}
}
-
public void ReplaceToText(string oldValue, Action buildText)
{
ThrowIfDisposed();
@@ -422,45 +420,75 @@ internal sealed class WordWriter : WordReader, IWordWriter
}
}
-
-
-
-
-
-
-
+ public void ReplaceString(string oldValue, Func newValues)
+ => ReplaceString(oldValue, (Func>)newValues);
public void ReplaceString(string oldValue, Func> newValues)
{
- throw new NotImplementedException();
- }
+ ThrowIfDisposed();
- public void ReplaceString(string oldValue, Func newValues)
- {
- throw new NotImplementedException();
- }
+ if (string.IsNullOrEmpty(oldValue))
+ throw new ArgumentException("Old value cannot be null or empty", nameof(oldValue));
+ if (newValues is null) return;
+
+#if DEBUG
+ Debug.WriteLine($"[DEBUG] [WordProcessor.ReplaceString(dynamic-multi)] START: '{oldValue}'");
+#endif
+
+ lock (_syncLock)
+ {
+ // 1. Основной текст
+ DynamicMultiReplaceExt.ReplaceStringMultiInBody(_body, oldValue, newValues, StringComparison.OrdinalIgnoreCase);
+
+ // 2. Таблицы в основном тексте
+ DynamicMultiReplaceExt.ReplaceStringMultiInTables(_body, oldValue, newValues, StringComparison.OrdinalIgnoreCase);
+
+ // 3. Колонтитулы
+ DynamicMultiReplaceExt.ReplaceStringMultiInHeadersFooters(_doc, oldValue, newValues, StringComparison.OrdinalIgnoreCase);
+
+ _isModified = true;
+ }
+ }
+ public void ReplaceItem(string oldValue, Func newValues)
+ => ReplaceItem(oldValue, (Func>)newValues);
public void ReplaceItem(string oldValue, Func> newValues)
{
- throw new NotImplementedException();
- }
+ ThrowIfDisposed();
- public void ReplaceItem(string oldValue, Func newValues)
- {
- throw new NotImplementedException();
+ if (string.IsNullOrEmpty(oldValue))
+ throw new ArgumentException("Old value cannot be null or empty", nameof(oldValue));
+
+ if (newValues is null) return;
+
+#if DEBUG
+ Debug.WriteLine($"[DEBUG] [WordProcessor.ReplaceItem(dynamic-multi)] START: '{oldValue}'");
+#endif
+
+ lock (_syncLock)
+ {
+ // 1. Основной текст
+ DynamicMultiReplaceExt.ReplaceItemMultiInBody(_body, oldValue, newValues, StringComparison.OrdinalIgnoreCase);
+
+ // 2. Таблицы в основном тексте
+ DynamicMultiReplaceExt.ReplaceItemMultiInTables(_body, oldValue, newValues, StringComparison.OrdinalIgnoreCase);
+
+ // 3. Колонтитулы
+ DynamicMultiReplaceExt.ReplaceItemMultiInHeadersFooters(_doc, oldValue, newValues, StringComparison.OrdinalIgnoreCase);
+
+ _isModified = true;
+ }
}
public void ReplaceToTable(string oldValue, Action buildTable)
{
throw new NotImplementedException();
}
-
public void ReplaceToTable(string oldValue, FontProps baseFont, Action buildTable)
{
throw new NotImplementedException();
}
-
-
+ #endregion