666
This commit is contained in:
@@ -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++)
|
||||
{
|
||||
@@ -552,26 +550,146 @@ internal static class MultiReplaceExt
|
||||
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,
|
||||
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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
bool addPageSize = (pendingOrientation.Value == BreakType.NewLandscapeSection);
|
||||
AddSectionProperties(currentPara, pendingOrientation.Value, addPageSize, sourceSection, portraitSection);
|
||||
lastOrientation = pendingOrientation;
|
||||
sectionChangeInsideGroup = true;
|
||||
@@ -586,7 +704,7 @@ internal static class MultiReplaceExt
|
||||
pageBreakBeforeNext = false;
|
||||
}
|
||||
|
||||
InsertFormattedRun(currentPara, item.Text, structure, match.Start);
|
||||
InsertFormattedRun(currentPara, text, structure, position);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -603,12 +721,27 @@ internal static class MultiReplaceExt
|
||||
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 (currentPara is not null)
|
||||
@@ -626,14 +759,11 @@ internal static class MultiReplaceExt
|
||||
}
|
||||
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;
|
||||
@@ -645,16 +775,13 @@ internal static class MultiReplaceExt
|
||||
}
|
||||
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)
|
||||
@@ -687,62 +814,9 @@ internal static class MultiReplaceExt
|
||||
}
|
||||
else
|
||||
{
|
||||
// Это не первый маркер — устанавливаем отложенную ориентацию для следующего текста
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user