множественные замены с параметром
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -402,7 +402,7 @@ internal static class MultiReplaceExt
|
||||
|
||||
// ---------- ОСНОВНОЙ АЛГОРИТМ ----------
|
||||
|
||||
private static List<Paragraph>? ProcessMultiReplacements(
|
||||
internal static List<Paragraph>? ProcessMultiReplacements(
|
||||
Paragraph original,
|
||||
IEnumerable<KeyValuePair<string, IEnumerable<string>>>? stringReplacements,
|
||||
IEnumerable<KeyValuePair<string, IEnumerable<ReplaceItem>>>? itemReplacements,
|
||||
@@ -848,4 +848,352 @@ internal static class MultiReplaceExt
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// НОВЫЕ ПУБЛИЧНЫЕ ТОЧКИ ВХОДА (mergeBoundaryText: true)
|
||||
// ============================================================
|
||||
|
||||
internal static List<Paragraph>? ProcessParagraphWithMergedBoundaries(
|
||||
Paragraph paragraph,
|
||||
IEnumerable<KeyValuePair<string, IEnumerable<string>>> replacements,
|
||||
StringComparison comparisonType)
|
||||
{
|
||||
return ProcessMultiReplacements(paragraph, replacements, null, comparisonType, mergeBoundaryText: true);
|
||||
}
|
||||
|
||||
internal static List<Paragraph>? ProcessParagraphWithMergedBoundaries(
|
||||
Paragraph paragraph,
|
||||
IEnumerable<KeyValuePair<string, IEnumerable<ReplaceItem>>> replacements,
|
||||
StringComparison comparisonType)
|
||||
{
|
||||
return ProcessMultiReplacements(paragraph, null, replacements, comparisonType, mergeBoundaryText: true);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// ОСНОВНОЙ АЛГОРИТМ (изменена только сигнатура + 4 места)
|
||||
// ============================================================
|
||||
|
||||
private static List<Paragraph>? ProcessMultiReplacements(
|
||||
Paragraph original,
|
||||
IEnumerable<KeyValuePair<string, IEnumerable<string>>>? stringReplacements,
|
||||
IEnumerable<KeyValuePair<string, IEnumerable<ReplaceItem>>>? itemReplacements,
|
||||
StringComparison comparisonType,
|
||||
bool mergeBoundaryText = false) // <-- новый параметр
|
||||
{
|
||||
var body = original.Ancestors<Body>().FirstOrDefault();
|
||||
|
||||
bool hasEmptyParagraphsBefore = false;
|
||||
var prevPara = original.PreviousSibling<Paragraph>();
|
||||
while (prevPara is not null)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(prevPara.InnerText)) break;
|
||||
hasEmptyParagraphsBefore = true;
|
||||
prevPara = prevPara.PreviousSibling<Paragraph>();
|
||||
}
|
||||
|
||||
SectionProperties? followingSection = null;
|
||||
if (original.ParagraphProperties?.GetFirstChild<SectionProperties>()?.CloneNode(true) is not SectionProperties sourceSection)
|
||||
{
|
||||
var nextPara = original.NextSibling<Paragraph>();
|
||||
if (nextPara is not null)
|
||||
{
|
||||
followingSection = nextPara.ParagraphProperties?.GetFirstChild<SectionProperties>()?.CloneNode(true) as SectionProperties;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
followingSection = sourceSection;
|
||||
}
|
||||
followingSection ??= new SectionProperties();
|
||||
|
||||
// 1. Сбор определений
|
||||
var definitions = new List<MatchDefinition>();
|
||||
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<Run>().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<Match>();
|
||||
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<SectionProperties>().FirstOrDefault()?.CloneNode(true) as SectionProperties;
|
||||
portraitSection ??= new SectionProperties();
|
||||
|
||||
// 5. Построение результата
|
||||
var resultParas = new List<Paragraph>();
|
||||
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<Run>? 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<Text>().ToList()) t.Remove();
|
||||
string textPart = seg.Text.Substring(startOffset, endOffset - startOffset);
|
||||
runClone.AppendChild(new Text(textPart));
|
||||
beforePara.AppendChild(runClone);
|
||||
}
|
||||
}
|
||||
|
||||
if (beforePara.ChildElements.OfType<Run>().Any())
|
||||
{
|
||||
if (!mergeBoundaryText)
|
||||
{
|
||||
resultParas.Add(beforePara);
|
||||
currentPara = beforePara;
|
||||
lastTextPara = beforePara;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Копим runs, склеим их с первым value-параграфом
|
||||
pendingBeforeRuns = new List<Run>();
|
||||
foreach (var r in beforePara.Elements<Run>())
|
||||
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<Text>().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<SectionProperties>() 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<PageSize>();
|
||||
if (sourcePageSize is not null)
|
||||
{
|
||||
if (currentPara.ParagraphProperties?.GetFirstChild<SectionProperties>() 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<Run>().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<PageSize>();
|
||||
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<Run>().Any() && p.ParagraphProperties is null)
|
||||
resultParas.RemoveAt(i);
|
||||
}
|
||||
|
||||
return resultParas.Count > 0 ? resultParas : null;
|
||||
}
|
||||
}
|
||||
@@ -344,7 +344,7 @@ internal sealed class WordWriter : WordReader, IWordWriter
|
||||
|
||||
|
||||
|
||||
|
||||
#region динамические змены с параметром
|
||||
|
||||
public void ReplaceString(string oldValue, Func<string, string> newValues)
|
||||
{
|
||||
@@ -373,7 +373,6 @@ internal sealed class WordWriter : WordReader, IWordWriter
|
||||
_isModified = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void ReplaceItem(string oldValue, Func<string, ReplaceItem> newValues)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
@@ -401,7 +400,6 @@ internal sealed class WordWriter : WordReader, IWordWriter
|
||||
_isModified = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void ReplaceToText(string oldValue, Action<string, IText> buildText)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
@@ -422,45 +420,75 @@ internal sealed class WordWriter : WordReader, IWordWriter
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public void ReplaceString(string oldValue, Func<string, string[]> newValues)
|
||||
=> ReplaceString(oldValue, (Func<string, IEnumerable<string>>)newValues);
|
||||
public void ReplaceString(string oldValue, Func<string, IEnumerable<string>> newValues)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
ThrowIfDisposed();
|
||||
|
||||
public void ReplaceString(string oldValue, Func<string, string[]> newValues)
|
||||
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)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
// 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<string, ReplaceItem[]> newValues)
|
||||
=> ReplaceItem(oldValue, (Func<string, IEnumerable<ReplaceItem>>)newValues);
|
||||
public void ReplaceItem(string oldValue, Func<string, IEnumerable<ReplaceItem>> newValues)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
ThrowIfDisposed();
|
||||
|
||||
public void ReplaceItem(string oldValue, Func<string, ReplaceItem[]> newValues)
|
||||
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)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
// 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<string, ITable> buildTable)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void ReplaceToTable(string oldValue, FontProps baseFont, Action<string, ITable> buildTable)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user