666
This commit is contained in:
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user