diff --git a/QWERTYkez.WordProcessor/MultiReplace.cs b/QWERTYkez.WordProcessor/MultiReplace.cs index 496fdd2..8b1d439 100644 --- a/QWERTYkez.WordProcessor/MultiReplace.cs +++ b/QWERTYkez.WordProcessor/MultiReplace.cs @@ -238,10 +238,6 @@ internal static class MultiReplaceExt para.AppendChild(textRun); } - /// - /// Добавляет SectionProperties к параграфу. Все значения (PageSize, PageMargin) берутся из документа. - /// Для книжных секций (addPageSize=false) Orient не устанавливается (not set). - /// private static void AddSectionProperties( Paragraph para, BreakType splitValue, @@ -406,6 +402,8 @@ internal static class MultiReplaceExt target.AppendChild(child.CloneNode(true)); } + // ---------- ОСНОВНОЙ АЛГОРИТМ ---------- + /// Основной алгоритм множественной замены с поддержкой разрывов и смены ориентации. private static List? 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().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() is null) - { - lastTextPara.ParagraphProperties ??= new ParagraphProperties(); - - var sourcePageSize = sourceSection.GetFirstChild(); - 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(); + if (sourcePageSize is not null) + { + if (currentPara.ParagraphProperties?.GetFirstChild() 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(); + 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 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 resultParas) + { + if (breakType == BreakType.PageBreak) + { + if (currentPara is not null) + { + var breakRun = new Run(new Break { Type = BreakValues.Page }); + var lastRun = currentPara.Descendants().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() is null) + { + lastTextPara.ParagraphProperties ??= new ParagraphProperties(); + + var sourcePageSize = sourceSection.GetFirstChild(); + 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; + } + } + } + } } \ No newline at end of file