табличные замены с параметром
This commit is contained in:
@@ -2,8 +2,7 @@
|
||||
|
||||
/// <summary>
|
||||
/// Множественные динамические замены с извлечением параметра из "$key|param$".
|
||||
/// Переиспользует движок <see cref="MultiReplaceExt"/> (форматирование Run-ов,
|
||||
/// разрывы страниц, смена ориентации, разбиение на параграфы — всё как в оригинале).
|
||||
/// Переиспользует движок <see cref="MultiReplaceExt"/>
|
||||
/// </summary>
|
||||
internal static class DynamicMultiReplaceExt
|
||||
{
|
||||
@@ -101,7 +100,7 @@ internal static class DynamicMultiReplaceExt
|
||||
catch { continue; }
|
||||
if (values is null) continue;
|
||||
|
||||
var list = values as IList<string> ?? values.ToList();
|
||||
var list = values as IList<string> ?? [.. values];
|
||||
if (list.Count == 0) continue;
|
||||
|
||||
dict[matchedText] = list;
|
||||
@@ -206,7 +205,7 @@ internal static class DynamicMultiReplaceExt
|
||||
catch { continue; }
|
||||
if (values is null) continue;
|
||||
|
||||
var list = values as IList<ReplaceItem> ?? values.ToList();
|
||||
var list = values as IList<ReplaceItem> ?? [.. values];
|
||||
if (list.Count == 0) continue;
|
||||
|
||||
dict[matchedText] = list;
|
||||
|
||||
@@ -0,0 +1,216 @@
|
||||
namespace QWERTYkez.WordProcessor;
|
||||
|
||||
/// <summary>
|
||||
/// Динамическая замена параграфа, содержащего "$key|param$", на таблицу.
|
||||
/// Логика построения и подмены — 1:1 копия из <see cref="ReplaceToTableExt"/>,
|
||||
/// плюс извлечение параметра.
|
||||
/// </summary>
|
||||
internal static class DynamicReplaceToTableExt
|
||||
{
|
||||
/// <summary>
|
||||
/// Заменяет все параграфы, содержащие шаблон "$key|...$", на таблицы.
|
||||
/// </summary>
|
||||
/// <param name="buildTable">Делегат (param, tableBuilder) — параметр из "$key|param$" и билдер таблицы.</param>
|
||||
internal static void ReplaceParagraphsContainingTextToTableDynamic(
|
||||
Body body,
|
||||
string oldValue,
|
||||
Action<string, ITable> buildTable,
|
||||
FontProps? baseFont = null)
|
||||
{
|
||||
if (body is null || string.IsNullOrEmpty(oldValue) || buildTable is null)
|
||||
return;
|
||||
|
||||
DynamicReplaceExt.ParsePattern(oldValue, out var prefix, out var suffix);
|
||||
|
||||
var paragraphs = body.Elements<Paragraph>().ToList();
|
||||
if (paragraphs.Count == 0)
|
||||
return;
|
||||
|
||||
for (int i = paragraphs.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var paragraph = paragraphs[i];
|
||||
if (paragraph is null) continue;
|
||||
|
||||
var paraText = paragraph.InnerText;
|
||||
if (string.IsNullOrEmpty(paraText)) continue;
|
||||
|
||||
int pStart = paraText.IndexOf(prefix, StringComparison.OrdinalIgnoreCase);
|
||||
if (pStart < 0) continue;
|
||||
|
||||
int paramStart = pStart + prefix.Length;
|
||||
int pEnd = string.IsNullOrEmpty(suffix)
|
||||
? paraText.Length
|
||||
: paraText.IndexOf(suffix, paramStart, StringComparison.OrdinalIgnoreCase);
|
||||
if (pEnd < 0) continue;
|
||||
|
||||
string param = paraText.Substring(paramStart, pEnd - paramStart);
|
||||
string matchedText = paraText.Substring(pStart, pEnd + suffix.Length - pStart);
|
||||
|
||||
// Извлекаем свойства шрифта из Run-а, содержащего совпадение
|
||||
var fontProps = ExtractFontPropsFromParagraph(paragraph, matchedText);
|
||||
|
||||
if (fontProps is { } props)
|
||||
fontProps = props.Override(baseFont);
|
||||
else if (baseFont is not null)
|
||||
fontProps = baseFont;
|
||||
|
||||
ReplaceWithTableBuilderDynamic(paragraph, param, buildTable, fontProps);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Создаёт TableBuilder, вызывает пользовательский делегат, заменяет параграф таблицей.
|
||||
/// (Копия <see cref="ReplaceToTableExt.ReplaceWithTableBuilder"/> с добавленным param.)
|
||||
/// </summary>
|
||||
private static void ReplaceWithTableBuilderDynamic(
|
||||
Paragraph paragraph,
|
||||
string param,
|
||||
Action<string, ITable> buildTable,
|
||||
FontProps? baseFont)
|
||||
{
|
||||
if (paragraph is null || paragraph.Parent is null || buildTable is null)
|
||||
return;
|
||||
|
||||
var parent = paragraph.Parent;
|
||||
|
||||
var builder = TableBuilder.Create(baseFont);
|
||||
buildTable(param, builder);
|
||||
var table = builder.Build();
|
||||
|
||||
int paraIndex = -1;
|
||||
var children = parent.ChildElements;
|
||||
|
||||
for (int i = 0; i < children.Count; i++)
|
||||
{
|
||||
if (children[i] == paragraph)
|
||||
{
|
||||
paraIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (paraIndex == -1)
|
||||
return;
|
||||
|
||||
parent.RemoveChild(paragraph);
|
||||
parent.InsertAt(table, paraIndex);
|
||||
}
|
||||
|
||||
private static FontProps? ExtractFontPropsFromParagraph(Paragraph paragraph, string searchText)
|
||||
{
|
||||
if (paragraph is null || string.IsNullOrEmpty(searchText))
|
||||
return null;
|
||||
|
||||
var runs = paragraph.Descendants<Run>().ToList();
|
||||
if (runs.Count == 0)
|
||||
return null;
|
||||
|
||||
var runInfos = new List<(Run Run, int Start, int End, string Text)>();
|
||||
int currentPosition = 0;
|
||||
|
||||
foreach (var run in runs)
|
||||
{
|
||||
var runText = GetRunText(run);
|
||||
if (!string.IsNullOrEmpty(runText))
|
||||
{
|
||||
runInfos.Add((run, currentPosition, currentPosition + runText.Length, runText));
|
||||
currentPosition += runText.Length;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var (run, start, end, text) in runInfos)
|
||||
{
|
||||
if (text.IndexOf(searchText, StringComparison.OrdinalIgnoreCase) >= 0)
|
||||
return CreateFontPropsFromRun(run);
|
||||
}
|
||||
|
||||
var fullText = string.Concat(runInfos.Select(r => r.Text));
|
||||
int textIndex = fullText.IndexOf(searchText, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
if (textIndex >= 0)
|
||||
{
|
||||
var intersectingRun = runInfos.FirstOrDefault(r =>
|
||||
r.Start <= textIndex && r.End > textIndex);
|
||||
|
||||
if (intersectingRun.Run is not null)
|
||||
return CreateFontPropsFromRun(intersectingRun.Run);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static FontProps CreateFontPropsFromRun(Run run)
|
||||
{
|
||||
var runProperties = run.RunProperties;
|
||||
if (runProperties is null)
|
||||
return new FontProps();
|
||||
|
||||
var fontProps = new FontProps();
|
||||
|
||||
var runFonts = runProperties.GetFirstChild<RunFonts>();
|
||||
if (runFonts is not null)
|
||||
{
|
||||
fontProps = fontProps with
|
||||
{
|
||||
FontFamily = runFonts.Ascii ?? runFonts.HighAnsi ?? runFonts.ComplexScript
|
||||
};
|
||||
}
|
||||
|
||||
var fontSize = runProperties.GetFirstChild<FontSize>();
|
||||
if (fontSize is not null && !string.IsNullOrEmpty(fontSize.Val) &&
|
||||
uint.TryParse(fontSize.Val, out uint halfPoints))
|
||||
{
|
||||
fontProps = fontProps with { Size = halfPoints / 2.0 };
|
||||
}
|
||||
|
||||
var bold = runProperties.GetFirstChild<Bold>();
|
||||
if (bold is not null)
|
||||
{
|
||||
bool isBold = true;
|
||||
if (bold.Val is not null)
|
||||
isBold = bold.Val.Value;
|
||||
fontProps = fontProps with { IsBold = isBold };
|
||||
}
|
||||
|
||||
var italic = runProperties.GetFirstChild<Italic>();
|
||||
if (italic is not null)
|
||||
{
|
||||
bool isItalic = true;
|
||||
if (italic.Val is not null)
|
||||
isItalic = italic.Val.Value;
|
||||
fontProps = fontProps with { IsItalic = isItalic };
|
||||
}
|
||||
|
||||
var underline = runProperties.GetFirstChild<Underline>();
|
||||
if (underline is not null && underline.Val is not null)
|
||||
{
|
||||
fontProps = fontProps with { Underline = underline.Val.Value };
|
||||
}
|
||||
|
||||
var color = runProperties.GetFirstChild<Color>();
|
||||
if (color is not null && !string.IsNullOrEmpty(color.Val))
|
||||
{
|
||||
fontProps = fontProps with { Color = color.Val };
|
||||
}
|
||||
|
||||
var verticalAlignment = runProperties.GetFirstChild<VerticalTextAlignment>();
|
||||
if (verticalAlignment is not null && verticalAlignment.Val is not null)
|
||||
{
|
||||
fontProps = fontProps with { SubSup = verticalAlignment.Val.Value };
|
||||
}
|
||||
|
||||
return fontProps;
|
||||
}
|
||||
|
||||
private static string GetRunText(Run run)
|
||||
{
|
||||
if (run is null) return string.Empty;
|
||||
|
||||
var sb = new StringBuilder();
|
||||
foreach (var text in run.Elements<Text>())
|
||||
{
|
||||
sb.Append(text.Text ?? string.Empty);
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
@@ -1011,7 +1011,7 @@ internal static class MultiReplaceExt
|
||||
else
|
||||
{
|
||||
// Копим runs, склеим их с первым value-параграфом
|
||||
pendingBeforeRuns = new List<Run>();
|
||||
pendingBeforeRuns = [];
|
||||
foreach (var r in beforePara.Elements<Run>())
|
||||
pendingBeforeRuns.Add((Run)r.CloneNode(true));
|
||||
}
|
||||
|
||||
@@ -479,13 +479,48 @@ internal sealed class WordWriter : WordReader, IWordWriter
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Динамические замены на таблицу с извлечением параметра из "$key|$"
|
||||
// ============================================================
|
||||
|
||||
public void ReplaceToTable(string oldValue, Action<string, ITable> buildTable)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
ThrowIfDisposed();
|
||||
|
||||
if (string.IsNullOrEmpty(oldValue))
|
||||
throw new ArgumentException("Old value cannot be null or empty", nameof(oldValue));
|
||||
|
||||
if (buildTable is null) return;
|
||||
|
||||
#if DEBUG
|
||||
Debug.WriteLine($"[DEBUG] [WordProcessor.ReplaceToTable(dynamic)] START: '{oldValue}'");
|
||||
#endif
|
||||
|
||||
lock (_syncLock)
|
||||
{
|
||||
DynamicReplaceToTableExt.ReplaceParagraphsContainingTextToTableDynamic(_body, oldValue, buildTable);
|
||||
_isModified = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void ReplaceToTable(string oldValue, FontProps baseFont, Action<string, ITable> buildTable)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
ThrowIfDisposed();
|
||||
|
||||
if (string.IsNullOrEmpty(oldValue))
|
||||
throw new ArgumentException("Old value cannot be null or empty", nameof(oldValue));
|
||||
|
||||
if (buildTable is null) return;
|
||||
|
||||
#if DEBUG
|
||||
Debug.WriteLine($"[DEBUG] [WordProcessor.ReplaceToTable(dynamic,font)] START: '{oldValue}'");
|
||||
#endif
|
||||
|
||||
lock (_syncLock)
|
||||
{
|
||||
DynamicReplaceToTableExt.ReplaceParagraphsContainingTextToTableDynamic(_body, oldValue, buildTable, baseFont);
|
||||
_isModified = true;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
Reference in New Issue
Block a user