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); para.AppendChild(textRun);
} }
/// <summary>
/// Добавляет SectionProperties к параграфу. Все значения (PageSize, PageMargin) берутся из документа.
/// Для книжных секций (addPageSize=false) Orient не устанавливается (not set).
/// </summary>
private static void AddSectionProperties( private static void AddSectionProperties(
Paragraph para, Paragraph para,
BreakType splitValue, BreakType splitValue,
@@ -406,6 +402,8 @@ internal static class MultiReplaceExt
target.AppendChild(child.CloneNode(true)); target.AppendChild(child.CloneNode(true));
} }
// ---------- ОСНОВНОЙ АЛГОРИТМ ----------
/// <summary>Основной алгоритм множественной замены с поддержкой разрывов и смены ориентации.</summary> /// <summary>Основной алгоритм множественной замены с поддержкой разрывов и смены ориентации.</summary>
private static List<Paragraph>? ProcessMultiReplacements( private static List<Paragraph>? ProcessMultiReplacements(
Paragraph original, Paragraph original,
@@ -501,7 +499,7 @@ internal static class MultiReplaceExt
bool firstSplitMarkerHandled = false; bool firstSplitMarkerHandled = false;
int textCount = 0; int textCount = 0;
Paragraph? lastTextPara = null; Paragraph? lastTextPara = null;
bool emptyParagraphCreated = false; // был ли создан пустой параграф для not set bool emptyParagraphCreated = false;
for (int i = 0; i < matches.Count; i++) for (int i = 0; i < matches.Count; i++)
{ {
@@ -553,145 +551,40 @@ internal static class MultiReplaceExt
{ {
if (!string.IsNullOrEmpty(item.Text)) if (!string.IsNullOrEmpty(item.Text))
{ {
// Если есть отложенная ориентация — применяем её ProcessTextItem(
if (pendingOrientation.HasValue) item.Text,
{ ref pendingOrientation,
var newPara = CloneParagraphWithoutSection(original); ref currentPara,
resultParas.Add(newPara); ref lastTextPara,
currentPara = newPara; ref textCount,
ref pageBreakBeforeNext,
// Определяем addPageSize: ref sectionChangeInsideGroup,
// - если был создан пустой параграф для not set (emptyParagraphCreated == true), то первый текст получает addPageSize = true для landscape, false для portrait ref lastOrientation,
// - иначе addPageSize = (pendingOrientation == BreakType.NewLandscapeSection) original,
bool addPageSize; structure,
if (emptyParagraphCreated) match.Start,
{ sourceSection,
addPageSize = (pendingOrientation.Value == BreakType.NewLandscapeSection); portraitSection,
} resultParas);
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);
}
} }
else if (item.BreakValue.HasValue) else if (item.BreakValue.HasValue)
{ {
var breakType = item.BreakValue.Value; ProcessBreakItem(
if (breakType == BreakType.PageBreak) item.BreakValue.Value,
{ ref pendingOrientation,
if (currentPara is not null) ref currentPara,
{ ref lastTextPara,
var breakRun = new Run(new Break { Type = BreakValues.Page }); ref pageBreakBeforeNext,
var lastRun = currentPara.Descendants<Run>().LastOrDefault(); ref firstSplitMarkerHandled,
if (lastRun?.RunProperties is not null) ref sectionChangeInsideGroup,
breakRun.RunProperties = (RunProperties)lastRun.RunProperties.CloneNode(true); ref lastOrientation,
currentPara.AppendChild(breakRun); ref textCount,
} ref emptyParagraphCreated,
else hasEmptyParagraphsBefore,
{ original,
pageBreakBeforeNext = true; sourceSection,
} portraitSection,
} resultParas);
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;
}
}
}
} }
} }
@@ -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); EnsureBodyLandscapeSection(body!, sourceSection, sectionChangeInsideGroup, lastOrientation);
currentPos = match.End; currentPos = match.End;
@@ -716,6 +624,7 @@ internal static class MultiReplaceExt
var remainderPara = BuildRemainderParagraph(original, structure, currentPos); var remainderPara = BuildRemainderParagraph(original, structure, currentPos);
if (remainderPara is not null) if (remainderPara is not null)
{ {
// Если есть отложенная ориентация — применяем её
if (pendingOrientation.HasValue) if (pendingOrientation.HasValue)
{ {
var breakRun = new Run(new Break { Type = BreakValues.Page }); var breakRun = new Run(new Break { Type = BreakValues.Page });
@@ -731,6 +640,16 @@ internal static class MultiReplaceExt
bool addPageSize = (lastOrientation.Value == BreakType.NewLandscapeSection); bool addPageSize = (lastOrientation.Value == BreakType.NewLandscapeSection);
AddSectionProperties(remainderPara, lastOrientation.Value, addPageSize, sourceSection, portraitSection); 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); resultParas.Add(remainderPara);
} }
} }
@@ -745,4 +664,159 @@ internal static class MultiReplaceExt
return resultParas.Count > 0 ? resultParas : null; 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;
}
}
}
}
} }