This commit is contained in:
melekhin
2026-08-10 15:23:45 +07:00
parent b023d2755e
commit fa77b2ce39
+215 -141
View File
@@ -238,10 +238,6 @@ internal static class MultiReplaceExt
para.AppendChild(textRun);
}
/// <summary>
/// Добавляет SectionProperties к параграфу. Все значения (PageSize, PageMargin) берутся из документа.
/// Для книжных секций (addPageSize=false) Orient не устанавливается (not set).
/// </summary>
private static void AddSectionProperties(
Paragraph para,
BreakType splitValue,
@@ -406,6 +402,8 @@ internal static class MultiReplaceExt
target.AppendChild(child.CloneNode(true));
}
// ---------- ОСНОВНОЙ АЛГОРИТМ ----------
/// <summary>Основной алгоритм множественной замены с поддержкой разрывов и смены ориентации.</summary>
private static List<Paragraph>? ProcessMultiReplacements(
Paragraph original,
@@ -501,7 +499,7 @@ internal static class MultiReplaceExt
bool firstSplitMarkerHandled = false;
int textCount = 0;
Paragraph? lastTextPara = null;
bool emptyParagraphCreated = false; // был ли создан пустой параграф для not set
bool emptyParagraphCreated = false;
for (int i = 0; i < matches.Count; i++)
{
@@ -553,145 +551,40 @@ internal static class MultiReplaceExt
{
if (!string.IsNullOrEmpty(item.Text))
{
// Если есть отложенная ориентация — применяем её
if (pendingOrientation.HasValue)
{
var newPara = CloneParagraphWithoutSection(original);
resultParas.Add(newPara);
currentPara = newPara;
// Определяем addPageSize:
// - если был создан пустой параграф для not set (emptyParagraphCreated == true), то первый текст получает addPageSize = true для landscape, false для portrait
// - иначе addPageSize = (pendingOrientation == BreakType.NewLandscapeSection)
bool addPageSize;
if (emptyParagraphCreated)
{
addPageSize = (pendingOrientation.Value == BreakType.NewLandscapeSection);
}
else
{
addPageSize = (pendingOrientation.Value == BreakType.NewLandscapeSection);
}
AddSectionProperties(currentPara, pendingOrientation.Value, addPageSize, sourceSection, portraitSection);
lastOrientation = pendingOrientation;
sectionChangeInsideGroup = true;
pendingOrientation = null;
textCount++;
lastTextPara = currentPara;
if (pageBreakBeforeNext)
{
var breakRun = new Run(new Break { Type = BreakValues.Page });
currentPara.InsertAt(breakRun, 0);
pageBreakBeforeNext = false;
}
InsertFormattedRun(currentPara, item.Text, structure, match.Start);
}
else
{
var newPara = CloneParagraphWithoutSection(original);
resultParas.Add(newPara);
currentPara = newPara;
textCount++;
lastTextPara = currentPara;
if (pageBreakBeforeNext)
{
var breakRun = new Run(new Break { Type = BreakValues.Page });
currentPara.InsertAt(breakRun, 0);
pageBreakBeforeNext = false;
}
InsertFormattedRun(currentPara, item.Text, structure, match.Start);
}
ProcessTextItem(
item.Text,
ref pendingOrientation,
ref currentPara,
ref lastTextPara,
ref textCount,
ref pageBreakBeforeNext,
ref sectionChangeInsideGroup,
ref lastOrientation,
original,
structure,
match.Start,
sourceSection,
portraitSection,
resultParas);
}
else if (item.BreakValue.HasValue)
{
var breakType = item.BreakValue.Value;
if (breakType == BreakType.PageBreak)
{
if (currentPara is not null)
{
var breakRun = new Run(new Break { Type = BreakValues.Page });
var lastRun = currentPara.Descendants<Run>().LastOrDefault();
if (lastRun?.RunProperties is not null)
breakRun.RunProperties = (RunProperties)lastRun.RunProperties.CloneNode(true);
currentPara.AppendChild(breakRun);
}
else
{
pageBreakBeforeNext = true;
}
}
else if (breakType == BreakType.NewLandscapeSection || breakType == BreakType.NewPortraitSection)
{
// Если это первый маркер в группе и перед original есть пустые параграфы,
// создаём пустой параграф с not set
if (currentPara is null && hasEmptyParagraphsBefore && !firstSplitMarkerHandled)
{
var newPara = CloneParagraphWithoutSection(original);
resultParas.Add(newPara);
currentPara = newPara;
// Добавляем секцию not set (без PageSize)
AddSectionProperties(currentPara, breakType, false, sourceSection, portraitSection);
lastOrientation = breakType;
sectionChangeInsideGroup = true;
firstSplitMarkerHandled = true;
emptyParagraphCreated = true;
pendingOrientation = null;
textCount = 0;
lastTextPara = null;
}
else if (currentPara is null && !hasEmptyParagraphsBefore && !firstSplitMarkerHandled)
{
// Нет пустых параграфов, нет текущего параграфа — запоминаем ориентацию для первого текста
pendingOrientation = breakType;
firstSplitMarkerHandled = true;
// emptyParagraphCreated остаётся false
}
else if (currentPara is not null)
{
if (!firstSplitMarkerHandled)
{
// Это первый маркер смены в группе (после текста)
if (lastTextPara is not null)
{
if (lastTextPara.ParagraphProperties?.GetFirstChild<SectionProperties>() is null)
{
lastTextPara.ParagraphProperties ??= new ParagraphProperties();
var sourcePageSize = sourceSection.GetFirstChild<PageSize>();
if (sourcePageSize is not null)
{
lastTextPara.ParagraphProperties.AppendChild(sourceSection.CloneNode(true));
}
else
{
AddSectionProperties(lastTextPara, breakType, false, sourceSection, portraitSection);
}
lastOrientation = breakType;
sectionChangeInsideGroup = true;
}
firstSplitMarkerHandled = true;
pendingOrientation = null;
}
else
{
AddSectionProperties(currentPara, breakType, false, sourceSection, portraitSection);
lastOrientation = breakType;
sectionChangeInsideGroup = true;
firstSplitMarkerHandled = true;
pendingOrientation = null;
}
}
else
{
// Это не первый маркер — устанавливаем отложенную ориентацию для следующего текста
pendingOrientation = breakType;
}
}
}
ProcessBreakItem(
item.BreakValue.Value,
ref pendingOrientation,
ref currentPara,
ref lastTextPara,
ref pageBreakBeforeNext,
ref firstSplitMarkerHandled,
ref sectionChangeInsideGroup,
ref lastOrientation,
ref textCount,
ref emptyParagraphCreated,
hasEmptyParagraphsBefore,
original,
sourceSection,
portraitSection,
resultParas);
}
}
@@ -705,6 +598,21 @@ internal static class MultiReplaceExt
}
}
// Если внутри группы не было смены, но есть sourceSection с PageSize,
// копируем её в последний параграф группы (если он не имеет секции)
if (!sectionChangeInsideGroup && currentPara is not null)
{
var sourcePageSize = sourceSection.GetFirstChild<PageSize>();
if (sourcePageSize is not null)
{
if (currentPara.ParagraphProperties?.GetFirstChild<SectionProperties>() is null)
{
currentPara.ParagraphProperties ??= new ParagraphProperties();
currentPara.ParagraphProperties.AppendChild(sourceSection.CloneNode(true));
}
}
}
EnsureBodyLandscapeSection(body!, sourceSection, sectionChangeInsideGroup, lastOrientation);
currentPos = match.End;
@@ -716,6 +624,7 @@ internal static class MultiReplaceExt
var remainderPara = BuildRemainderParagraph(original, structure, currentPos);
if (remainderPara is not null)
{
// Если есть отложенная ориентация — применяем её
if (pendingOrientation.HasValue)
{
var breakRun = new Run(new Break { Type = BreakValues.Page });
@@ -731,6 +640,16 @@ internal static class MultiReplaceExt
bool addPageSize = (lastOrientation.Value == BreakType.NewLandscapeSection);
AddSectionProperties(remainderPara, lastOrientation.Value, addPageSize, sourceSection, portraitSection);
}
else
{
// Если нет смены, но есть sourceSection с PageSize, копируем её в остаток
var sourcePageSize = sourceSection.GetFirstChild<PageSize>();
if (sourcePageSize is not null)
{
remainderPara.ParagraphProperties ??= new ParagraphProperties();
remainderPara.ParagraphProperties.AppendChild(sourceSection.CloneNode(true));
}
}
resultParas.Add(remainderPara);
}
}
@@ -745,4 +664,159 @@ internal static class MultiReplaceExt
return resultParas.Count > 0 ? resultParas : null;
}
// ---------- ВСПОМОГАТЕЛЬНЫЕ МЕТОДЫ ДЛЯ ОБРАБОТКИ ГРУПП ----------
private static void ProcessTextItem(
string text,
ref BreakType? pendingOrientation,
ref Paragraph? currentPara,
ref Paragraph? lastTextPara,
ref int textCount,
ref bool pageBreakBeforeNext,
ref bool sectionChangeInsideGroup,
ref BreakType? lastOrientation,
Paragraph original,
ParagraphStructure structure,
int position,
SectionProperties sourceSection,
SectionProperties portraitSection,
List<Paragraph> resultParas)
{
if (pendingOrientation.HasValue)
{
var newPara = CloneParagraphWithoutSection(original);
resultParas.Add(newPara);
currentPara = newPara;
bool addPageSize = (pendingOrientation.Value == BreakType.NewLandscapeSection);
AddSectionProperties(currentPara, pendingOrientation.Value, addPageSize, sourceSection, portraitSection);
lastOrientation = pendingOrientation;
sectionChangeInsideGroup = true;
pendingOrientation = null;
textCount++;
lastTextPara = currentPara;
if (pageBreakBeforeNext)
{
var breakRun = new Run(new Break { Type = BreakValues.Page });
currentPara.InsertAt(breakRun, 0);
pageBreakBeforeNext = false;
}
InsertFormattedRun(currentPara, text, structure, position);
}
else
{
var newPara = CloneParagraphWithoutSection(original);
resultParas.Add(newPara);
currentPara = newPara;
textCount++;
lastTextPara = currentPara;
if (pageBreakBeforeNext)
{
var breakRun = new Run(new Break { Type = BreakValues.Page });
currentPara.InsertAt(breakRun, 0);
pageBreakBeforeNext = false;
}
InsertFormattedRun(currentPara, text, structure, position);
}
}
private static void ProcessBreakItem(
BreakType breakType,
ref BreakType? pendingOrientation,
ref Paragraph? currentPara,
ref Paragraph? lastTextPara,
ref bool pageBreakBeforeNext,
ref bool firstSplitMarkerHandled,
ref bool sectionChangeInsideGroup,
ref BreakType? lastOrientation,
ref int textCount,
ref bool emptyParagraphCreated,
bool hasEmptyParagraphsBefore,
Paragraph original,
SectionProperties sourceSection,
SectionProperties portraitSection,
List<Paragraph> resultParas)
{
if (breakType == BreakType.PageBreak)
{
if (currentPara is not null)
{
var breakRun = new Run(new Break { Type = BreakValues.Page });
var lastRun = currentPara.Descendants<Run>().LastOrDefault();
if (lastRun?.RunProperties is not null)
breakRun.RunProperties = (RunProperties)lastRun.RunProperties.CloneNode(true);
currentPara.AppendChild(breakRun);
}
else
{
pageBreakBeforeNext = true;
}
}
else if (breakType == BreakType.NewLandscapeSection || breakType == BreakType.NewPortraitSection)
{
if (currentPara is null && hasEmptyParagraphsBefore && !firstSplitMarkerHandled)
{
var newPara = CloneParagraphWithoutSection(original);
resultParas.Add(newPara);
currentPara = newPara;
AddSectionProperties(currentPara, breakType, false, sourceSection, portraitSection);
lastOrientation = breakType;
sectionChangeInsideGroup = true;
firstSplitMarkerHandled = true;
emptyParagraphCreated = true;
pendingOrientation = null;
textCount = 0;
lastTextPara = null;
}
else if (currentPara is null && !hasEmptyParagraphsBefore && !firstSplitMarkerHandled)
{
pendingOrientation = breakType;
firstSplitMarkerHandled = true;
}
else if (currentPara is not null)
{
if (!firstSplitMarkerHandled)
{
if (lastTextPara is not null)
{
if (lastTextPara.ParagraphProperties?.GetFirstChild<SectionProperties>() is null)
{
lastTextPara.ParagraphProperties ??= new ParagraphProperties();
var sourcePageSize = sourceSection.GetFirstChild<PageSize>();
if (sourcePageSize is not null)
{
lastTextPara.ParagraphProperties.AppendChild(sourceSection.CloneNode(true));
}
else
{
AddSectionProperties(lastTextPara, breakType, false, sourceSection, portraitSection);
}
lastOrientation = breakType;
sectionChangeInsideGroup = true;
}
firstSplitMarkerHandled = true;
pendingOrientation = null;
}
else
{
AddSectionProperties(currentPara, breakType, false, sourceSection, portraitSection);
lastOrientation = breakType;
sectionChangeInsideGroup = true;
firstSplitMarkerHandled = true;
pendingOrientation = null;
}
}
else
{
pendingOrientation = breakType;
}
}
}
}
}