Remove ICellText
All checks were successful
Publish NuGet packages / publish (push) Successful in 27s

This commit is contained in:
melekhin
2026-06-17 09:33:21 +07:00
parent eccb12b83c
commit c9ef2a796e
3 changed files with 576 additions and 620 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -1,270 +0,0 @@
namespace QWERTYkez.ExcelProcessor;
/// <summary>
/// Внутренняя реализация <see cref="ICellText"/> для работы с богатым текстом ячейки.
/// Хранит коллекцию фрагментов <see cref="ExcelRun"/>.
/// Минимизирует аллокации, не использует рефлексию.
/// </summary>
internal sealed class ExcelCellText : ICellText
{
private List<IRun>? _runs;
/// <inheritdoc />
public int Count => _runs?.Count ?? 0;
/// <inheritdoc />
public IEnumerable<IRun> GetRuns()
{
if (_runs is null)
return [];
// Возвращаем сам список, чтобы избежать копирования.
// Вызывающий не должен модифицировать коллекцию.
return _runs;
}
/// <inheritdoc />
public IRun? GetRunAt(int index)
{
if (_runs is null || index < 0 || index >= _runs.Count)
return null;
return _runs[index];
}
/// <inheritdoc />
public bool TryGetRunAt(int index, out IRun run)
{
run = GetRunAt(index)!;
return run != null;
}
/// <inheritdoc />
public IRun? First()
{
if (_runs is null || _runs.Count == 0)
return null;
return _runs[0];
}
/// <inheritdoc />
public bool TryGetFirst(out IRun run)
{
run = First()!;
return run != null;
}
/// <inheritdoc />
public IRun? Last()
{
if (_runs is null || _runs.Count == 0)
return null;
return _runs[_runs.Count - 1];
}
/// <inheritdoc />
public bool TryGetLast(out IRun run)
{
run = Last()!;
return run != null;
}
/// <inheritdoc />
public bool TryRemoveRun(IRun run)
{
if (run is null || _runs is null)
return false;
return _runs.Remove(run);
}
/// <inheritdoc />
public bool TryRemoveRun(int index)
{
if (_runs is null || index < 0 || index >= _runs.Count)
return false;
_runs.RemoveAt(index);
return true;
}
/// <inheritdoc />
public bool TryRemoveRun(int index, out IRun? removed)
{
removed = GetRunAt(index);
if (removed is null)
return false;
return TryRemoveRun(index);
}
/// <inheritdoc />
public ICellText Break()
{
// Добавляем символ переноса строки в последний существующий Run
if (_runs != null && _runs.Count > 0)
{
var lastRun = _runs[_runs.Count - 1];
lastRun.Text += "\n";
}
else
{
// Если нет ни одного Run, создаём новый с символом переноса
Run("\n", null);
}
return this;
}
/// <inheritdoc />
public ICellText Run(string text, RunFormat? format = null)
{
if (string.IsNullOrEmpty(text))
return this;
_runs ??= [];
var run = new ExcelRun { Text = text, Format = format };
_runs.Add(run);
return this;
}
/// <inheritdoc />
public ICellText RunBreak(string text, RunFormat? format = null)
{
Run(text, format);
Break();
return this;
}
/// <inheritdoc />
public ICellText Sub(string text, RunFormat? format = null)
{
var subFormat = format is { } fmt
? new RunFormat
{
IsBold = fmt.IsBold,
IsItalic = fmt.IsItalic,
Underline = fmt.Underline,
IsStrike = fmt.IsStrike,
Color = fmt.Color,
FontSize = fmt.FontSize,
FontFamily = fmt.FontFamily,
Vertical = VerticalTextRunAlignment.Subscript
}
: new RunFormat { Vertical = VerticalTextRunAlignment.Subscript };
return Run(text, subFormat);
}
/// <inheritdoc />
public ICellText Sup(string text, RunFormat? format = null)
{
var supFormat = format is { } fmt
? new RunFormat
{
IsBold = fmt.IsBold,
IsItalic = fmt.IsItalic,
Underline = fmt.Underline,
IsStrike = fmt.IsStrike,
Color = fmt.Color,
FontSize = fmt.FontSize,
FontFamily = fmt.FontFamily,
Vertical = VerticalTextRunAlignment.Subscript
}
: new RunFormat { Vertical = VerticalTextRunAlignment.Superscript };
return Run(text, supFormat);
}
/// <inheritdoc />
public bool TryInsertRun(int index, string text, RunFormat? format = null)
{
if (index < 0 || string.IsNullOrEmpty(text))
return false;
_runs ??= [];
if (index > _runs.Count)
return false;
var run = new ExcelRun { Text = text, Format = format };
_runs.Insert(index, run);
return true;
}
/// <inheritdoc />
public bool TryInsertRunBreak(int index, string text, RunFormat? format = null)
{
if (!TryInsertRun(index, text, format))
return false;
// После вставленного run добавляем break на следующей позиции
Break();
// Сдвигаем? Просто добавляем break в конец неверно. Break должен быть сразу после вставленного.
// Но AddBreak добавляет в конец. Нужно вставить break на index+1.
return TryInsertRun(index + 1, "\n", null);
}
/// <inheritdoc />
public bool TryInsertSub(int index, string text, RunFormat? format = null)
{
var subFormat = format is { } fmt
? new RunFormat
{
IsBold = fmt.IsBold,
IsItalic = fmt.IsItalic,
Underline = fmt.Underline,
IsStrike = fmt.IsStrike,
Color = fmt.Color,
FontSize = fmt.FontSize,
FontFamily = fmt.FontFamily,
Vertical = VerticalTextRunAlignment.Subscript
}
: new RunFormat { Vertical = VerticalTextRunAlignment.Subscript };
return TryInsertRun(index, text, subFormat);
}
/// <inheritdoc />
public bool TryInsertSup(int index, string text, RunFormat? format = null)
{
var supFormat = format is { } fmt
? new RunFormat
{
IsBold = fmt.IsBold,
IsItalic = fmt.IsItalic,
Underline = fmt.Underline,
IsStrike = fmt.IsStrike,
Color = fmt.Color,
FontSize = fmt.FontSize,
FontFamily = fmt.FontFamily,
Vertical = VerticalTextRunAlignment.Subscript
}
: new RunFormat { Vertical = VerticalTextRunAlignment.Superscript };
return TryInsertRun(index, text, supFormat);
}
/// <inheritdoc />
public void ApplyFormatToAllRuns(RunFormat format)
{
if (_runs is null || _runs.Count == 0)
return;
foreach (var run in _runs)
{
if (run is ExcelRun xRun)
{
// Объединение форматов: ненулевые свойства overlay заменяют значения в base.
var baseFmt = xRun.Format ?? new RunFormat();
xRun.Format = MergeRunFormat(baseFmt, format);
}
}
}
/// <inheritdoc />
public void Clear()
{
_runs?.Clear();
_runs = null;
}
private static RunFormat MergeRunFormat(RunFormat baseFmt, RunFormat overlay)
{
return new RunFormat
{
IsBold = overlay.IsBold ?? baseFmt.IsBold,
IsItalic = overlay.IsItalic ?? baseFmt.IsItalic,
Underline = overlay.Underline ?? baseFmt.Underline,
IsStrike = overlay.IsStrike ?? baseFmt.IsStrike,
Color = overlay.Color ?? baseFmt.Color,
FontSize = overlay.FontSize ?? baseFmt.FontSize,
FontFamily = overlay.FontFamily ?? baseFmt.FontFamily,
Vertical = overlay.Vertical ?? baseFmt.Vertical
};
}
}

View File

@@ -560,6 +560,69 @@ public interface IRange
/// <summary>Представляет одну ячейку на листе.</summary> /// <summary>Представляет одну ячейку на листе.</summary>
public interface ICell public interface ICell
{ {
/// <summary>Количество фрагментов (Run) в тексте.</summary>
int RunsCount { get; }
/// <summary>Возвращает все фрагменты.</summary>
IEnumerable<IRun> GetRuns();
/// <summary>Возвращает фрагмент по индексу или null.</summary>
IRun? GetRunAt(int index);
/// <summary>Пытается получить фрагмент по индексу.</summary>
bool TryGetRunAt(int index, out IRun run);
/// <summary>Первый фрагмент или null.</summary>
IRun? First();
/// <summary>Пытается получить первый фрагмент.</summary>
bool TryGetFirst(out IRun run);
/// <summary>Последний фрагмент или null.</summary>
IRun? Last();
/// <summary>Пытается получить последний фрагмент.</summary>
bool TryGetLast(out IRun run);
/// <summary>Пытается удалить фрагмент.</summary>
bool TryRemoveRun(IRun run);
/// <summary>Удаляет фрагмент по индексу.</summary>
bool TryRemoveRun(int index);
/// <summary>Удаляет фрагмент по индексу и возвращает удалённый.</summary>
bool TryRemoveRun(int index, out IRun? removed);
/// <summary>Добавляет разрыв строки (перенос внутри ячейки).</summary>
ICell Break();
/// <summary>Добавляет обычный текстовый фрагмент.</summary>
ICell Run(string text, RunFormat? format = null);
/// <summary>Добавляет фрагмент с последующим разрывом строки.</summary>
ICell RunBreak(string text, RunFormat? format = null);
/// <summary>Добавляет подстрочный фрагмент (эквивалентно AddRun с Vertical = Subscript).</summary>
ICell Sub(string text, RunFormat? format = null);
/// <summary>Добавляет надстрочный фрагмент.</summary>
ICell Sup(string text, RunFormat? format = null);
/// <summary>Вставляет фрагмент по индексу.</summary>
bool TryInsertRun(int index, string text, RunFormat? format = null);
/// <summary>Вставляет фрагмент с последующим разрывом строки по индексу.</summary>
bool TryInsertRunBreak(int index, string text, RunFormat? format = null);
/// <summary>Вставляет подстрочный фрагмент по индексу.</summary>
bool TryInsertSub(int index, string text, RunFormat? format = null);
/// <summary>Вставляет надстрочный фрагмент по индексу.</summary>
bool TryInsertSup(int index, string text, RunFormat? format = null);
/// <summary>Применяет заданный формат ко всем существующим фрагментам (поверх их текущего форматирования, заменяя неуказанные свойства).</summary>
void ApplyFormatToAllRuns(RunFormat format);
/// <summary>Проверяет, входит ли ячейка в объединённый диапазон.</summary> /// <summary>Проверяет, входит ли ячейка в объединённый диапазон.</summary>
bool IsMerged { get; } bool IsMerged { get; }
@@ -699,15 +762,6 @@ public interface ICell
/// <summary>Устанавливает шрифт ячейки.</summary> /// <summary>Устанавливает шрифт ячейки.</summary>
ICell Set(CellFont format); ICell Set(CellFont format);
/// <summary>Устанавливает богатый текст (форматированный) с помощью делегата.</summary>
ICell Text(Action<ICellText> value);
/// <summary>Возвращает объект для редактирования богатого текста ячейки (если ячейка содержит InlineString).</summary>
bool TryText(out ICellText cellText);
/// <summary>Возвращает объект для редактирования богатого текста ячейки (если ячейка содержит InlineString).</summary>
ICellText Text();
/// <summary>Устанавливает простое текстовое значение (без форматирования).</summary> /// <summary>Устанавливает простое текстовое значение (без форматирования).</summary>
ICell Set(string value); ICell Set(string value);
@@ -742,76 +796,6 @@ public interface ICell
void Clear(); void Clear();
} }
/// <summary>Представляет богатый текст внутри ячейки (несколько форматированных фрагментов).</summary>
public interface ICellText
{
/// <summary>Количество фрагментов (Run) в тексте.</summary>
int Count { get; }
/// <summary>Возвращает все фрагменты.</summary>
IEnumerable<IRun> GetRuns();
/// <summary>Возвращает фрагмент по индексу или null.</summary>
IRun? GetRunAt(int index);
/// <summary>Пытается получить фрагмент по индексу.</summary>
bool TryGetRunAt(int index, out IRun run);
/// <summary>Первый фрагмент или null.</summary>
IRun? First();
/// <summary>Пытается получить первый фрагмент.</summary>
bool TryGetFirst(out IRun run);
/// <summary>Последний фрагмент или null.</summary>
IRun? Last();
/// <summary>Пытается получить последний фрагмент.</summary>
bool TryGetLast(out IRun run);
/// <summary>Пытается удалить фрагмент.</summary>
bool TryRemoveRun(IRun run);
/// <summary>Удаляет фрагмент по индексу.</summary>
bool TryRemoveRun(int index);
/// <summary>Удаляет фрагмент по индексу и возвращает удалённый.</summary>
bool TryRemoveRun(int index, out IRun? removed);
/// <summary>Добавляет разрыв строки (перенос внутри ячейки).</summary>
ICellText Break();
/// <summary>Добавляет обычный текстовый фрагмент.</summary>
ICellText Run(string text, RunFormat? format = null);
/// <summary>Добавляет фрагмент с последующим разрывом строки.</summary>
ICellText RunBreak(string text, RunFormat? format = null);
/// <summary>Добавляет подстрочный фрагмент (эквивалентно AddRun с Vertical = Subscript).</summary>
ICellText Sub(string text, RunFormat? format = null);
/// <summary>Добавляет надстрочный фрагмент.</summary>
ICellText Sup(string text, RunFormat? format = null);
/// <summary>Вставляет фрагмент по индексу.</summary>
bool TryInsertRun(int index, string text, RunFormat? format = null);
/// <summary>Вставляет фрагмент с последующим разрывом строки по индексу.</summary>
bool TryInsertRunBreak(int index, string text, RunFormat? format = null);
/// <summary>Вставляет подстрочный фрагмент по индексу.</summary>
bool TryInsertSub(int index, string text, RunFormat? format = null);
/// <summary>Вставляет надстрочный фрагмент по индексу.</summary>
bool TryInsertSup(int index, string text, RunFormat? format = null);
/// <summary>Применяет заданный формат ко всем существующим фрагментам (поверх их текущего форматирования, заменяя неуказанные свойства).</summary>
void ApplyFormatToAllRuns(RunFormat format);
/// <summary>Удаляет все фрагменты, очищая текст ячейки.</summary>
void Clear();
}
/// <summary>Представляет один форматированный фрагмент текста внутри ячейки.</summary> /// <summary>Представляет один форматированный фрагмент текста внутри ячейки.</summary>
public interface IRun public interface IRun
{ {