This commit is contained in:
melekhin
2026-08-10 15:23:45 +07:00
parent b023d2755e
commit fa77b2ce39
+154 -80
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++)
{ {
@@ -552,26 +550,146 @@ internal static class MultiReplaceExt
foreach (var item in values) foreach (var item in values)
{ {
if (!string.IsNullOrEmpty(item.Text)) 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,
sourceSection,
portraitSection,
resultParas);
}
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,
sourceSection,
portraitSection,
resultParas);
}
}
// Закрываем секцию, если была смена
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, sourceSection, portraitSection);
}
}
// Если внутри группы не было смены, но есть 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;
}
// 6. Обработка остатка текста
if (currentPos < fullText.Length)
{
var remainderPara = BuildRemainderParagraph(original, structure, currentPos);
if (remainderPara is not null)
{ {
// Если есть отложенная ориентация — применяем её // Если есть отложенная ориентация — применяем её
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, sourceSection, 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, 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);
}
}
// 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;
}
// ---------- ВСПОМОГАТЕЛЬНЫЕ МЕТОДЫ ДЛЯ ОБРАБОТКИ ГРУПП ----------
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) if (pendingOrientation.HasValue)
{ {
var newPara = CloneParagraphWithoutSection(original); var newPara = CloneParagraphWithoutSection(original);
resultParas.Add(newPara); resultParas.Add(newPara);
currentPara = newPara; currentPara = newPara;
// Определяем addPageSize: bool addPageSize = (pendingOrientation.Value == BreakType.NewLandscapeSection);
// - если был создан пустой параграф для 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); AddSectionProperties(currentPara, pendingOrientation.Value, addPageSize, sourceSection, portraitSection);
lastOrientation = pendingOrientation; lastOrientation = pendingOrientation;
sectionChangeInsideGroup = true; sectionChangeInsideGroup = true;
@@ -586,7 +704,7 @@ internal static class MultiReplaceExt
pageBreakBeforeNext = false; pageBreakBeforeNext = false;
} }
InsertFormattedRun(currentPara, item.Text, structure, match.Start); InsertFormattedRun(currentPara, text, structure, position);
} }
else else
{ {
@@ -603,12 +721,27 @@ internal static class MultiReplaceExt
pageBreakBeforeNext = false; pageBreakBeforeNext = false;
} }
InsertFormattedRun(currentPara, item.Text, structure, match.Start); InsertFormattedRun(currentPara, text, structure, position);
} }
} }
else if (item.BreakValue.HasValue)
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)
{ {
var breakType = item.BreakValue.Value;
if (breakType == BreakType.PageBreak) if (breakType == BreakType.PageBreak)
{ {
if (currentPara is not null) if (currentPara is not null)
@@ -626,14 +759,11 @@ internal static class MultiReplaceExt
} }
else if (breakType == BreakType.NewLandscapeSection || breakType == BreakType.NewPortraitSection) else if (breakType == BreakType.NewLandscapeSection || breakType == BreakType.NewPortraitSection)
{ {
// Если это первый маркер в группе и перед original есть пустые параграфы,
// создаём пустой параграф с not set
if (currentPara is null && hasEmptyParagraphsBefore && !firstSplitMarkerHandled) if (currentPara is null && hasEmptyParagraphsBefore && !firstSplitMarkerHandled)
{ {
var newPara = CloneParagraphWithoutSection(original); var newPara = CloneParagraphWithoutSection(original);
resultParas.Add(newPara); resultParas.Add(newPara);
currentPara = newPara; currentPara = newPara;
// Добавляем секцию not set (без PageSize)
AddSectionProperties(currentPara, breakType, false, sourceSection, portraitSection); AddSectionProperties(currentPara, breakType, false, sourceSection, portraitSection);
lastOrientation = breakType; lastOrientation = breakType;
sectionChangeInsideGroup = true; sectionChangeInsideGroup = true;
@@ -645,16 +775,13 @@ internal static class MultiReplaceExt
} }
else if (currentPara is null && !hasEmptyParagraphsBefore && !firstSplitMarkerHandled) else if (currentPara is null && !hasEmptyParagraphsBefore && !firstSplitMarkerHandled)
{ {
// Нет пустых параграфов, нет текущего параграфа — запоминаем ориентацию для первого текста
pendingOrientation = breakType; pendingOrientation = breakType;
firstSplitMarkerHandled = true; firstSplitMarkerHandled = true;
// emptyParagraphCreated остаётся false
} }
else if (currentPara is not null) else if (currentPara is not null)
{ {
if (!firstSplitMarkerHandled) if (!firstSplitMarkerHandled)
{ {
// Это первый маркер смены в группе (после текста)
if (lastTextPara is not null) if (lastTextPara is not null)
{ {
if (lastTextPara.ParagraphProperties?.GetFirstChild<SectionProperties>() is null) if (lastTextPara.ParagraphProperties?.GetFirstChild<SectionProperties>() is null)
@@ -687,62 +814,9 @@ internal static class MultiReplaceExt
} }
else else
{ {
// Это не первый маркер — устанавливаем отложенную ориентацию для следующего текста
pendingOrientation = breakType; pendingOrientation = breakType;
} }
} }
} }
} }
} }
// Закрываем секцию, если была смена
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, sourceSection, portraitSection);
}
}
EnsureBodyLandscapeSection(body!, sourceSection, sectionChangeInsideGroup, lastOrientation);
currentPos = match.End;
}
// 6. Обработка остатка текста
if (currentPos < fullText.Length)
{
var remainderPara = BuildRemainderParagraph(original, structure, currentPos);
if (remainderPara is not null)
{
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, sourceSection, 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, sourceSection, portraitSection);
}
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;
}
}