From c9ef2a796e95202c12e24e7500b362167c2bd6e4 Mon Sep 17 00:00:00 2001 From: melekhin Date: Wed, 17 Jun 2026 09:33:21 +0700 Subject: [PATCH] Remove ICellText --- QWERTYkez.ExcelProcessor/Editors/ExcelCell.cs | 784 ++++++++++++------ .../Editors/ExcelCellText.cs | 270 ------ .../Editors/Interfaces.cs | 142 ++-- 3 files changed, 576 insertions(+), 620 deletions(-) delete mode 100644 QWERTYkez.ExcelProcessor/Editors/ExcelCellText.cs diff --git a/QWERTYkez.ExcelProcessor/Editors/ExcelCell.cs b/QWERTYkez.ExcelProcessor/Editors/ExcelCell.cs index e0a9019..2f709e9 100644 --- a/QWERTYkez.ExcelProcessor/Editors/ExcelCell.cs +++ b/QWERTYkez.ExcelProcessor/Editors/ExcelCell.cs @@ -3,21 +3,450 @@ /// /// Внутренняя реализация . /// -internal sealed class ExcelCell : ICell +internal sealed class ExcelCell(ExcelWriter writer, ExcelSheet sheet, uint row, uint col) : ICell { - private readonly ExcelWriter _writer; - private readonly ExcelSheet _sheet; - private uint _row; - private uint _col; + // Кэш фрагментов богатого текста (только для InlineString) + private List? _runsCache; + private bool _cacheValid; - internal ExcelCell(ExcelWriter writer, ExcelSheet sheet, uint row, uint col) + // ---- Реализация ICell (новые методы) ---- + + public int RunsCount { - _writer = writer; - _sheet = sheet; - _row = row; - _col = col; + get + { + EnsureCacheValid(); + return _runsCache?.Count ?? 0; + } } + public IEnumerable GetRuns() + { + EnsureCacheValid(); + return _runsCache ?? Enumerable.Empty(); + } + + public IRun? GetRunAt(int index) + { + EnsureCacheValid(); + if (_runsCache is null || index < 0 || index >= _runsCache.Count) + return null; + return _runsCache[index]; + } + + public bool TryGetRunAt(int index, out IRun run) + { + run = GetRunAt(index)!; + return run != null; + } + + public IRun? First() + { + EnsureCacheValid(); + return _runsCache?.FirstOrDefault(); + } + + public bool TryGetFirst(out IRun run) + { + run = First()!; + return run != null; + } + + public IRun? Last() + { + EnsureCacheValid(); + return _runsCache?.LastOrDefault(); + } + + public bool TryGetLast(out IRun run) + { + run = Last()!; + return run != null; + } + + public bool TryRemoveRun(IRun run) + { + if (run is null) return false; + EnsureCacheValid(); + if (_runsCache is null) return false; + bool removed = _runsCache.Remove(run); + if (removed) + { + _cacheValid = true; + UpdateCellFromCache(); + } + return removed; + } + + public bool TryRemoveRun(int index) + { + EnsureCacheValid(); + if (_runsCache is null || index < 0 || index >= _runsCache.Count) + return false; + _runsCache.RemoveAt(index); + _cacheValid = true; + UpdateCellFromCache(); + return true; + } + + public bool TryRemoveRun(int index, out IRun? removed) + { + removed = GetRunAt(index); + if (removed is null) return false; + return TryRemoveRun(index); + } + + public ICell Break() + { + EnsureCacheValid(); + if (_runsCache != null && _runsCache.Count > 0) + { + var lastRun = _runsCache[_runsCache.Count - 1]; + lastRun.Text += "\n"; + } + else + { + Run("\n", null); + } + _cacheValid = true; + UpdateCellFromCache(); + return this; + } + + public ICell Run(string text, RunFormat? format = null) + { + if (string.IsNullOrEmpty(text)) return this; + EnsureCacheValid(); + _runsCache ??= []; + _runsCache.Add(new ExcelRun { Text = text, Format = format }); + _cacheValid = true; + UpdateCellFromCache(); + return this; + } + + public ICell RunBreak(string text, RunFormat? format = null) + { + Run(text, format); + Break(); + return this; + } + + public ICell 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); + } + + public ICell 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.Superscript + } + : new RunFormat { Vertical = VerticalTextRunAlignment.Superscript }; + return Run(text, supFormat); + } + + public bool TryInsertRun(int index, string text, RunFormat? format = null) + { + if (index < 0 || string.IsNullOrEmpty(text)) return false; + EnsureCacheValid(); + _runsCache ??= []; + if (index > _runsCache.Count) return false; + _runsCache.Insert(index, new ExcelRun { Text = text, Format = format }); + _cacheValid = true; + UpdateCellFromCache(); + return true; + } + + public bool TryInsertRunBreak(int index, string text, RunFormat? format = null) + { + if (!TryInsertRun(index, text, format)) return false; + return TryInsertRun(index + 1, "\n", null); + } + + 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); + } + + 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.Superscript + } + : new RunFormat { Vertical = VerticalTextRunAlignment.Superscript }; + return TryInsertRun(index, text, supFormat); + } + + public void ApplyFormatToAllRuns(RunFormat format) + { + EnsureCacheValid(); + if (_runsCache is null || _runsCache.Count == 0) return; + foreach (var run in _runsCache) + { + if (run is ExcelRun xRun) + { + var baseFmt = xRun.Format ?? new RunFormat(); + xRun.Format = MergeRunFormat(baseFmt, format); + } + } + _cacheValid = true; + UpdateCellFromCache(); + } + + public ICell ClearRuns() + { + _runsCache = null; + _cacheValid = true; + UpdateCellFromCache(); // устанавливаем пустой InlineString + return this; + } + + // ---- Существующие методы (частично изменены) ---- + + // (здесь остаются все старые методы: IsMerged, GetMergedRange, MoveTo, Height, Width, IsNumber, и т.д.) + // Они не меняются, за исключением методов Set и ClearContent, которые должны сбрасывать кэш. + + // Пример: метод Set(string) - сбрасываем кэш и устанавливаем обычный текст + public ICell Set(string value) + { + if (value == null) return this; + writer.ThrowIfDisposed(); + lock (writer._syncLock) + { + var cell = GetOrCreateCellElement(); + // Сбрасываем кэш + _runsCache = null; + _cacheValid = false; + + if (double.TryParse(value, out double num)) + { + cell.DataType = CellValues.Number; + cell.CellValue = new CellValue(num.ToString(CultureInfo.InvariantCulture)); + } + else + { + int idx = writer.GetOrAddSharedString(value); + cell.DataType = CellValues.SharedString; + cell.CellValue = new CellValue(idx.ToString()); + } + // Удаляем InlineString, если был + cell.InlineString = null; + cell.CellFormula = null; + } + return this; + } + + // Аналогично для Set(bool), Set(числа), Set(DateTime) - нужно сбрасывать кэш и удалять InlineString. + // Для краткости приведу только один метод, остальные аналогично. + + // Метод ClearContent - сбрасываем кэш + public void ClearContent() + { + writer.ThrowIfDisposed(); + lock (writer._syncLock) + { + var cell = GetCellElement(); + if (cell != null) + { + cell.CellValue = null; + cell.CellFormula = null; + cell.InlineString = null; + cell.DataType = null; + } + _runsCache = null; + _cacheValid = false; + } + } + + // Метод ClearFormat не трогает текст, поэтому кэш остаётся валидным (если был). + // Но если стиль меняется, кэш не сбрасываем. + + // ---- Приватные методы для работы с кэшем ---- + + private void EnsureCacheValid() + { + if (_cacheValid) return; + lock (writer._syncLock) + { + if (_cacheValid) return; + var cell = GetCellElement(); + _runsCache = []; + + if (cell != null && cell.DataType?.Value == CellValues.InlineString && cell.InlineString != null) + { + // Читаем InlineString + foreach (var run in cell.InlineString.Elements()) + { + string text = run.GetFirstChild()?.Text ?? string.Empty; + RunFormat? format = RunFormat.FromRunProperties(run.RunProperties!); + _runsCache.Add(new ExcelRun { Text = text, Format = format }); + } + } + else if (cell != null) + { + // Преобразуем содержимое в один Run (без форматирования) + string text = GetStringFromCell(cell); + if (!string.IsNullOrEmpty(text)) + _runsCache.Add(new ExcelRun { Text = text, Format = null }); + } + // Если ячейка пуста, кэш остаётся пустым + _cacheValid = true; + } + } + + private string GetStringFromCell(Cell cell) + { + if (cell == null) return string.Empty; + if (cell.DataType?.Value == CellValues.SharedString && cell.CellValue != null) + { + return writer.GetSharedString(uint.Parse(cell.CellValue.Text)); + } + if (cell.CellValue != null) + { + return cell.CellValue.Text; + } + return string.Empty; + } + + private void UpdateCellFromCache() + { + lock (writer._syncLock) + { + var cell = GetOrCreateCellElement(); + if (_runsCache == null || _runsCache.Count == 0) + { + // Устанавливаем пустой InlineString + cell.InlineString = new InlineString(); + cell.DataType = CellValues.InlineString; + cell.CellValue = null; + cell.CellFormula = null; + } + else + { + cell.InlineString = BuildInlineStringFromCache(); + cell.DataType = CellValues.InlineString; + cell.CellValue = null; + cell.CellFormula = null; + } + } + } + + private InlineString BuildInlineStringFromCache() + { + var inline = new InlineString(); + if (_runsCache == null) return inline; + foreach (var run in _runsCache) + { + var runElement = new Run(); + runElement.Append(new Text(run.Text)); + if (run.Format is { } fmt) + { + var rPr = new RunProperties(); + if (fmt.IsBold == true) rPr.Append(new Bold()); + if (fmt.IsItalic == true) rPr.Append(new Italic()); + if (fmt.Underline.HasValue) + { + rPr.Append(new Underline + { + Val = fmt.Underline.Value switch + { + UnderlineStyle.Single => UnderlineValues.Single, + UnderlineStyle.Double => UnderlineValues.Double, + UnderlineStyle.SingleAccounting => UnderlineValues.SingleAccounting, + UnderlineStyle.DoubleAccounting => UnderlineValues.DoubleAccounting, + _ => throw new NotImplementedException(), + } + }); + } + if (fmt.IsStrike == true) rPr.Append(new Strike()); + if (fmt.Color.HasValue) + { + var excelColor = fmt.Color.Value.ToExcel(); + rPr.Append(excelColor); + } + if (fmt.FontSize.HasValue) + rPr.Append(new FontSize { Val = fmt.FontSize.Value }); + if (!string.IsNullOrEmpty(fmt.FontFamily)) + rPr.Append(new RunFont { Val = fmt.FontFamily }); + if (fmt.Vertical.HasValue) + { + var vertAlign = new VerticalTextAlignment + { + Val = fmt.Vertical.Value == VerticalTextRunAlignment.Superscript + ? VerticalAlignmentRunValues.Superscript + : VerticalAlignmentRunValues.Subscript + }; + rPr.Append(vertAlign); + } + runElement.RunProperties = rPr; + } + inline.Append(runElement); + } + return inline; + } + + 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 + }; + } + + + + public bool IsMerged { get @@ -29,8 +458,8 @@ internal sealed class ExcelCell : ICell public IRange? GetMergedRange() { - _writer.ThrowIfDisposed(); - lock (_writer._syncLock) + writer.ThrowIfDisposed(); + lock (writer._syncLock) { var mergeCells = GetMergeCells(); if (mergeCells == null) return null; @@ -38,8 +467,8 @@ internal sealed class ExcelCell : ICell { if (TryParseRangeReference(mergeCell.Reference?.Value ?? string.Empty, out var range)) { - if (_row >= range.RowStart && _row <= range.RowEnd && - _col >= range.ColStart && _col <= range.ColEnd) + if (row >= range.RowStart && row <= range.RowEnd && + col >= range.ColStart && col <= range.ColEnd) return range; } } @@ -54,15 +483,15 @@ internal sealed class ExcelCell : ICell return merged.Equals(range); } - public uint Row => _row; - public uint Col => _col; - public string ColLetter => CellAddressHelper.ColumnIndexToLetter(_col); + public uint Row => row; + public uint Col => col; + public string ColLetter => CellAddressHelper.ColumnIndexToLetter(col); public ICell MoveTo(uint rowIndex, uint colIndex) { - if (rowIndex == _row && colIndex == _col) return this; - _writer.ThrowIfDisposed(); - lock (_writer._syncLock) + if (rowIndex == row && colIndex == col) return this; + writer.ThrowIfDisposed(); + lock (writer._syncLock) { // Вырезать-вставить: копируем данные в новую позицию, очищаем старую var srcCell = GetCellElement(); @@ -72,8 +501,8 @@ internal sealed class ExcelCell : ICell InsertCellAt(rowIndex, colIndex, cloned); srcCell.Remove(); } - _row = rowIndex; - _col = colIndex; + row = rowIndex; + col = colIndex; } return this; } @@ -82,14 +511,14 @@ internal sealed class ExcelCell : ICell public RowHeight Height { - get => ExcelRow.GetHeight(_writer, _sheet, _row); - set => ExcelRow.SetHeight(_writer, _sheet, _row, value); + get => ExcelRow.GetHeight(writer, sheet, row); + set => ExcelRow.SetHeight(writer, sheet, row, value); } public ColumnWidth Width { - get => ExcelColumn.GetWidth(_writer, _sheet, _col); - set => ExcelColumn.SetWidth(_writer, _sheet, _col, value); + get => ExcelColumn.GetWidth(writer, sheet, col); + set => ExcelColumn.SetWidth(writer, sheet, col, value); } public bool IsNumber @@ -157,7 +586,7 @@ internal sealed class ExcelCell : ICell var cell = GetCellElement(); if (cell?.StyleIndex?.Value is not uint styleIndex) return false; - return _writer.IsCellLocked(styleIndex); + return writer.IsCellLocked(styleIndex); } } public bool HasFormula => GetCellElement()?.CellFormula != null; @@ -192,7 +621,7 @@ internal sealed class ExcelCell : ICell var cell = GetCellElement(); if (cell?.StyleIndex?.Value is not uint styleIndex) return null; - return _writer.GetNumberFormat(styleIndex); + return writer.GetNumberFormat(styleIndex); } /// @@ -201,7 +630,7 @@ internal sealed class ExcelCell : ICell var cell = GetCellElement(); if (cell?.StyleIndex?.Value is not uint styleIndex) return default; - return _writer.GetCellAlign(styleIndex); + return writer.GetCellAlign(styleIndex); } /// @@ -210,7 +639,7 @@ internal sealed class ExcelCell : ICell var cell = GetCellElement(); if (cell?.StyleIndex?.Value is not uint styleIndex) return default; - return _writer.GetCellBorder(styleIndex); + return writer.GetCellBorder(styleIndex); } /// @@ -219,7 +648,7 @@ internal sealed class ExcelCell : ICell var cell = GetCellElement(); if (cell?.StyleIndex?.Value is not uint styleIndex) return default; - return _writer.GetCellFill(styleIndex); + return writer.GetCellFill(styleIndex); } /// @@ -228,7 +657,7 @@ internal sealed class ExcelCell : ICell var cell = GetCellElement(); if (cell?.StyleIndex?.Value is not uint styleIndex) return default; - return _writer.GetCellFont(styleIndex); + return writer.GetCellFont(styleIndex); } public bool TryGetBoolean(out bool value) @@ -283,8 +712,8 @@ internal sealed class ExcelCell : ICell public bool TrySet(string formula, NumberFormatPattern? format = null) { if (string.IsNullOrEmpty(formula)) return false; - _writer.ThrowIfDisposed(); - lock (_writer._syncLock) + writer.ThrowIfDisposed(); + lock (writer._syncLock) { var cell = GetOrCreateCellElement(); cell.CellFormula = new CellFormula(formula); @@ -305,8 +734,8 @@ internal sealed class ExcelCell : ICell public ICell Set(NumberFormatPattern format) { if (format == null) return this; - _writer.ThrowIfDisposed(); - lock (_writer._syncLock) + writer.ThrowIfDisposed(); + lock (writer._syncLock) { var cell = GetOrCreateCellElement(); SetNumberFormatInternal(cell, format); @@ -317,11 +746,11 @@ internal sealed class ExcelCell : ICell /// public ICell Set(CellAlign align) { - _writer.ThrowIfDisposed(); - lock (_writer._syncLock) + writer.ThrowIfDisposed(); + lock (writer._syncLock) { var cell = GetOrCreateCellElement(); - int styleIndex = _writer.GetOrCreateCellFormatId( + int styleIndex = writer.GetOrCreateCellFormatId( numberFormat: null, font: null, fill: null, @@ -336,11 +765,11 @@ internal sealed class ExcelCell : ICell /// public ICell Set(CellBorder border) { - _writer.ThrowIfDisposed(); - lock (_writer._syncLock) + writer.ThrowIfDisposed(); + lock (writer._syncLock) { var cell = GetOrCreateCellElement(); - int styleIndex = _writer.GetOrCreateCellFormatId( + int styleIndex = writer.GetOrCreateCellFormatId( numberFormat: null, font: null, fill: null, @@ -355,11 +784,11 @@ internal sealed class ExcelCell : ICell /// public ICell Set(CellFill fill) { - _writer.ThrowIfDisposed(); - lock (_writer._syncLock) + writer.ThrowIfDisposed(); + lock (writer._syncLock) { var cell = GetOrCreateCellElement(); - int styleIndex = _writer.GetOrCreateCellFormatId( + int styleIndex = writer.GetOrCreateCellFormatId( numberFormat: null, font: null, fill: fill, @@ -374,11 +803,11 @@ internal sealed class ExcelCell : ICell /// public ICell Set(CellFont font) { - _writer.ThrowIfDisposed(); - lock (_writer._syncLock) + writer.ThrowIfDisposed(); + lock (writer._syncLock) { var cell = GetOrCreateCellElement(); - int styleIndex = _writer.GetOrCreateCellFormatId( + int styleIndex = writer.GetOrCreateCellFormatId( numberFormat: null, font: font, fill: null, @@ -390,126 +819,10 @@ internal sealed class ExcelCell : ICell } } - public ICell Text(Action value) - { - if (value == null) return this; - _writer.ThrowIfDisposed(); - lock (_writer._syncLock) - { - var cell = GetOrCreateCellElement(); - var textObj = new ExcelCellText(); - value(textObj); - cell.InlineString = BuildInlineString(textObj); - cell.DataType = CellValues.InlineString; - cell.CellValue = null; - cell.CellFormula = null; - - // Если есть разрыв строки (символ \n), включаем перенос текста для ячейки - bool hasNewline = textObj.GetRuns().Any(r => r.Text != null && r.Text.Contains('\n')); - if (hasNewline) - { - var currentAlign = GetCellAlign(); - if (currentAlign.WrapText != true) - { - var newAlign = new CellAlign - { - Horizontal = currentAlign.Horizontal, - Vertical = currentAlign.Vertical, - WrapText = true, - ShrinkToFit = currentAlign.ShrinkToFit - }; - Set(newAlign); - } - } - } - return this; - } - - /// - public ICellText Text() - { - _writer.ThrowIfDisposed(); - lock (_writer._syncLock) - { - // 1. Получаем или создаём элемент ячейки - var cell = GetOrCreateCellElement(); - - // 2. Убедимся, что у ячейки правильный тип и есть InlineString - if (cell.DataType == null || cell.DataType.Value != CellValues.InlineString) - { - // Если был другой тип (например, число) — меняем на InlineString - cell.DataType = CellValues.InlineString; - // Удаляем старый InlineString, если был - cell.InlineString?.Remove(); - // Создаём новый пустой InlineString - cell.InlineString = new InlineString(); - // Добавляем пустой Text (обязательно для Open XML) - cell.InlineString.AppendChild(new Text()); - } - else if (cell.InlineString == null) - { - // Если тип InlineString, но сам элемент отсутствует - cell.InlineString = new InlineString(); - cell.InlineString.AppendChild(new Text()); - } - - // 3. Строим объект ICellText на основе содержимого InlineString - var textObj = new ExcelCellText(); - foreach (var run in cell.InlineString.Elements()) - { - string text = run.GetFirstChild()?.Text ?? string.Empty; - RunFormat? format = null; - if (run.RunProperties != null) - { - format = RunFormat.FromRunProperties(run.RunProperties); - } - textObj.Run(text, format); - } - - // Если не было ни одного Run, текст всё равно должен быть доступен (пустой) - // Можно оставить textObj пустым, а можно сразу добавить пустой Run - // (но это не обязательно – пользователь может добавить run позже) - return textObj; - } - } - - /// - public bool TryText(out ICellText cellText) - { - cellText = Text()!; - return cellText is not null; - } - - public ICell Set(string value) - { - if (value == null) return this; - _writer.ThrowIfDisposed(); - lock (_writer._syncLock) - { - var cell = GetOrCreateCellElement(); - // Проверяем, можно ли сохранить как число? - if (double.TryParse(value, out double num)) - { - cell.DataType = CellValues.Number; - cell.CellValue = new CellValue(num.ToString()); - } - else - { - // Используем общую таблицу строк - int idx = GetOrAddSharedString(value); - cell.DataType = CellValues.SharedString; - cell.CellValue = new CellValue(idx.ToString()); - } - cell.InlineString = null; - cell.CellFormula = null; - } - return this; - } - public ICell Set(bool value) { - _writer.ThrowIfDisposed(); - lock (_writer._syncLock) + writer.ThrowIfDisposed(); + lock (writer._syncLock) { var cell = GetOrCreateCellElement(); cell.DataType = CellValues.Boolean; @@ -530,8 +843,8 @@ internal sealed class ExcelCell : ICell public ICell Set(decimal value, NumberFormatPattern? format = null) => Set((double)value, format); public ICell Set(double value, NumberFormatPattern? format = null) { - _writer.ThrowIfDisposed(); - lock (_writer._syncLock) + writer.ThrowIfDisposed(); + lock (writer._syncLock) { var cell = GetOrCreateCellElement(); cell.DataType = CellValues.Number; @@ -545,26 +858,10 @@ internal sealed class ExcelCell : ICell public ICell Set(int value, NumberFormatPattern? format = null) => Set((double)value, format); public ICell Set(long value, NumberFormatPattern? format = null) => Set((double)value, format); - public void ClearContent() - { - _writer.ThrowIfDisposed(); - lock (_writer._syncLock) - { - var cell = GetCellElement(); - if (cell != null) - { - cell.CellValue = null; - cell.CellFormula = null; - cell.InlineString = null; - cell.DataType = null; - } - } - } - public void ClearFormat() { - _writer.ThrowIfDisposed(); - lock (_writer._syncLock) + writer.ThrowIfDisposed(); + lock (writer._syncLock) { var cell = GetCellElement(); cell?.StyleIndex = null; @@ -579,19 +876,19 @@ internal sealed class ExcelCell : ICell public ICell CopyTo(uint rowIndex, uint colIndex, out ICell copiedCell) { - _writer.ThrowIfDisposed(); - lock (_writer._syncLock) + writer.ThrowIfDisposed(); + lock (writer._syncLock) { var srcCell = GetCellElement(); if (srcCell != null) { var cloned = (Cell)srcCell.CloneNode(true); InsertCellAt(rowIndex, colIndex, cloned); - copiedCell = new ExcelCell(_writer, _sheet, rowIndex, colIndex); + copiedCell = new ExcelCell(writer, sheet, rowIndex, colIndex); } else { - copiedCell = new ExcelCell(_writer, _sheet, rowIndex, colIndex); + copiedCell = new ExcelCell(writer, sheet, rowIndex, colIndex); } return this; } @@ -604,29 +901,29 @@ internal sealed class ExcelCell : ICell private Cell? GetCellElement() { - var sheetData = _sheet.GetSheetData(); - var row = FindRowElement(sheetData, _row); - if (row == null) return null; - return FindCellInRow(row, _col); + var sheetData = sheet.GetSheetData(); + var eRow = FindRowElement(sheetData, row); + if (eRow == null) return null; + return FindCellInRow(eRow, col); } private Cell GetOrCreateCellElement() { - var sheetData = _sheet.GetSheetData(); - var row = GetOrCreateRowElement(sheetData, _row); - var cell = FindCellInRow(row, _col); + var sheetData = sheet.GetSheetData(); + var eRow = GetOrCreateRowElement(sheetData, row); + var cell = FindCellInRow(eRow, col); if (cell != null) return cell; cell = new Cell(); - string cellRef = CellAddressHelper.ColumnIndexToLetter(_col) + _row.ToString(); + string cellRef = CellAddressHelper.ColumnIndexToLetter(col) + row.ToString(); cell.CellReference = cellRef; - InsertCellInRow(row, cell, _col); + InsertCellInRow(eRow, cell, col); return cell; } private static Row? FindRowElement(SheetData sheetData, uint rowIndex) { - foreach (var row in sheetData.Elements()) - if (row.RowIndex?.Value == rowIndex) return row; + foreach (var eRow in sheetData.Elements()) + if (eRow.RowIndex?.Value == rowIndex) return eRow; return null; } @@ -639,24 +936,24 @@ internal sealed class ExcelCell : ICell return newRow; } - private static void InsertRowElement(SheetData sheetData, Row row, uint rowIndex) + private static void InsertRowElement(SheetData sheetData, Row eRow, uint rowIndex) { bool inserted = false; foreach (var existing in sheetData.Elements().ToList()) { if (existing.RowIndex?.Value > rowIndex) { - existing.InsertBeforeSelf(row); + existing.InsertBeforeSelf(eRow); inserted = true; break; } } - if (!inserted) sheetData.Append(row); + if (!inserted) sheetData.Append(eRow); } - private static Cell? FindCellInRow(Row row, uint colIndex) + private static Cell? FindCellInRow(Row eRow, uint colIndex) { - foreach (var cell in row.Elements()) + foreach (var cell in eRow.Elements()) { if (CellAddressHelper.TryParseCellReference(cell.CellReference?.Value ?? string.Empty, out _, out uint col) && col == colIndex) return cell; @@ -664,12 +961,12 @@ internal sealed class ExcelCell : ICell return null; } - private static void InsertCellInRow(Row row, Cell cell, uint colIndex) + private static void InsertCellInRow(Row eRow, Cell cell, uint colIndex) { - string newRef = CellAddressHelper.ColumnIndexToLetter(colIndex) + (row.RowIndex?.Value ?? 1).ToString(); + string newRef = CellAddressHelper.ColumnIndexToLetter(colIndex) + (eRow.RowIndex?.Value ?? 1).ToString(); cell.CellReference = newRef; bool inserted = false; - foreach (var existing in row.Elements().ToList()) + foreach (var existing in eRow.Elements().ToList()) { if (CellAddressHelper.TryParseCellReference(existing.CellReference?.Value ?? string.Empty, out _, out uint existingCol) && existingCol > colIndex) { @@ -678,12 +975,12 @@ internal sealed class ExcelCell : ICell break; } } - if (!inserted) row.Append(cell); + if (!inserted) eRow.Append(cell); } private void InsertCellAt(uint rowIndex, uint colIndex, Cell cell) { - var sheetData = _sheet.GetSheetData(); + var sheetData = sheet.GetSheetData(); var row = GetOrCreateRowElement(sheetData, rowIndex); InsertCellInRow(row, cell, colIndex); } @@ -691,7 +988,7 @@ internal sealed class ExcelCell : ICell private void SetNumberFormatInternal(Cell cell, NumberFormatPattern format) { if (format == null) return; - int styleIndex = _writer.GetOrCreateCellFormatId( + int styleIndex = writer.GetOrCreateCellFormatId( numberFormat: format, font: null, fill: null, @@ -703,12 +1000,12 @@ internal sealed class ExcelCell : ICell private string GetSharedString(uint index) { - return _writer.GetSharedString(index); + return writer.GetSharedString(index); } private int GetOrAddSharedString(string value) { - return _writer.GetOrAddSharedString(value); + return writer.GetOrAddSharedString(value); } private string ExtractTextFromInlineString(InlineString? inlineString) @@ -723,63 +1020,8 @@ internal sealed class ExcelCell : ICell return sb.ToString(); } - private InlineString BuildInlineString(ExcelCellText textObj) - { - var inline = new InlineString(); - foreach (var run in textObj.GetRuns()) - { - var runElement = new Run(); - // Всегда создаём элемент Text, даже если строка состоит из "\n" - runElement.Append(new Text(run.Text)); - - if (run.Format is { } fmt) - { - var rPr = new RunProperties(); - if (fmt.IsBold == true) rPr.Append(new Bold()); - if (fmt.IsItalic == true) rPr.Append(new Italic()); - if (fmt.Underline.HasValue) - { - rPr.Append(new Underline - { - Val = fmt.Underline.Value switch - { - UnderlineStyle.Single => UnderlineValues.Single, - UnderlineStyle.Double => UnderlineValues.Double, - UnderlineStyle.SingleAccounting => UnderlineValues.SingleAccounting, - UnderlineStyle.DoubleAccounting => UnderlineValues.DoubleAccounting, - _ => throw new NotImplementedException(), - } - }); - } - if (fmt.IsStrike == true) rPr.Append(new Strike()); - if (fmt.Color.HasValue) - { - var excelColor = fmt.Color.Value.ToExcel(); - rPr.Append(excelColor); - } - if (fmt.FontSize.HasValue) - rPr.Append(new FontSize { Val = fmt.FontSize.Value }); - if (!string.IsNullOrEmpty(fmt.FontFamily)) - rPr.Append(new RunFont { Val = fmt.FontFamily }); - if (fmt.Vertical.HasValue) - { - var vertAlign = new VerticalTextAlignment - { - Val = fmt.Vertical.Value == VerticalTextRunAlignment.Superscript - ? VerticalAlignmentRunValues.Superscript - : VerticalAlignmentRunValues.Subscript - }; - rPr.Append(vertAlign); - } - runElement.RunProperties = rPr; - } - inline.Append(runElement); - } - return inline; - } - private MergeCells? GetMergeCells() => - _sheet.Worksheet.GetFirstChild(); + sheet.Worksheet.GetFirstChild(); private bool TryParseRangeReference(string reference, out ExcelRange range) { @@ -791,7 +1033,7 @@ internal sealed class ExcelCell : ICell if (!CellAddressHelper.TryParseCellReference(parts[1] + "1", out uint row2, out uint col2)) return false; uint rowStart = Math.Min(row1, row2), rowEnd = Math.Max(row1, row2); uint colStart = Math.Min(col1, col2), colEnd = Math.Max(col1, col2); - range = new ExcelRange(_writer, _sheet, rowStart, colStart, rowEnd, colEnd); + range = new ExcelRange(writer, sheet, rowStart, colStart, rowEnd, colEnd); return true; } } \ No newline at end of file diff --git a/QWERTYkez.ExcelProcessor/Editors/ExcelCellText.cs b/QWERTYkez.ExcelProcessor/Editors/ExcelCellText.cs deleted file mode 100644 index 0178834..0000000 --- a/QWERTYkez.ExcelProcessor/Editors/ExcelCellText.cs +++ /dev/null @@ -1,270 +0,0 @@ -namespace QWERTYkez.ExcelProcessor; - -/// -/// Внутренняя реализация для работы с богатым текстом ячейки. -/// Хранит коллекцию фрагментов . -/// Минимизирует аллокации, не использует рефлексию. -/// -internal sealed class ExcelCellText : ICellText -{ - private List? _runs; - - /// - public int Count => _runs?.Count ?? 0; - - /// - public IEnumerable GetRuns() - { - if (_runs is null) - return []; - // Возвращаем сам список, чтобы избежать копирования. - // Вызывающий не должен модифицировать коллекцию. - return _runs; - } - - /// - public IRun? GetRunAt(int index) - { - if (_runs is null || index < 0 || index >= _runs.Count) - return null; - return _runs[index]; - } - - /// - public bool TryGetRunAt(int index, out IRun run) - { - run = GetRunAt(index)!; - return run != null; - } - - /// - public IRun? First() - { - if (_runs is null || _runs.Count == 0) - return null; - return _runs[0]; - } - - /// - public bool TryGetFirst(out IRun run) - { - run = First()!; - return run != null; - } - - /// - public IRun? Last() - { - if (_runs is null || _runs.Count == 0) - return null; - return _runs[_runs.Count - 1]; - } - - /// - public bool TryGetLast(out IRun run) - { - run = Last()!; - return run != null; - } - - /// - public bool TryRemoveRun(IRun run) - { - if (run is null || _runs is null) - return false; - return _runs.Remove(run); - } - - /// - public bool TryRemoveRun(int index) - { - if (_runs is null || index < 0 || index >= _runs.Count) - return false; - _runs.RemoveAt(index); - return true; - } - - /// - public bool TryRemoveRun(int index, out IRun? removed) - { - removed = GetRunAt(index); - if (removed is null) - return false; - return TryRemoveRun(index); - } - - /// - 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; - } - - /// - 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; - } - - /// - public ICellText RunBreak(string text, RunFormat? format = null) - { - Run(text, format); - Break(); - return this; - } - - /// - 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); - } - - /// - 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); - } - - /// - 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; - } - - /// - 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); - } - - /// - 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); - } - - /// - 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); - } - - /// - 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); - } - } - } - - /// - 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 - }; - } -} \ No newline at end of file diff --git a/QWERTYkez.ExcelProcessor/Editors/Interfaces.cs b/QWERTYkez.ExcelProcessor/Editors/Interfaces.cs index 954f898..7d0abd0 100644 --- a/QWERTYkez.ExcelProcessor/Editors/Interfaces.cs +++ b/QWERTYkez.ExcelProcessor/Editors/Interfaces.cs @@ -560,6 +560,69 @@ public interface IRange /// Представляет одну ячейку на листе. public interface ICell { + /// Количество фрагментов (Run) в тексте. + int RunsCount { get; } + + /// Возвращает все фрагменты. + IEnumerable GetRuns(); + + /// Возвращает фрагмент по индексу или null. + IRun? GetRunAt(int index); + + /// Пытается получить фрагмент по индексу. + bool TryGetRunAt(int index, out IRun run); + + /// Первый фрагмент или null. + IRun? First(); + + /// Пытается получить первый фрагмент. + bool TryGetFirst(out IRun run); + + /// Последний фрагмент или null. + IRun? Last(); + + /// Пытается получить последний фрагмент. + bool TryGetLast(out IRun run); + + /// Пытается удалить фрагмент. + bool TryRemoveRun(IRun run); + + /// Удаляет фрагмент по индексу. + bool TryRemoveRun(int index); + + /// Удаляет фрагмент по индексу и возвращает удалённый. + bool TryRemoveRun(int index, out IRun? removed); + + /// Добавляет разрыв строки (перенос внутри ячейки). + ICell Break(); + + /// Добавляет обычный текстовый фрагмент. + ICell Run(string text, RunFormat? format = null); + + /// Добавляет фрагмент с последующим разрывом строки. + ICell RunBreak(string text, RunFormat? format = null); + + /// Добавляет подстрочный фрагмент (эквивалентно AddRun с Vertical = Subscript). + ICell Sub(string text, RunFormat? format = null); + + /// Добавляет надстрочный фрагмент. + ICell Sup(string text, RunFormat? format = null); + + /// Вставляет фрагмент по индексу. + bool TryInsertRun(int index, string text, RunFormat? format = null); + + /// Вставляет фрагмент с последующим разрывом строки по индексу. + bool TryInsertRunBreak(int index, string text, RunFormat? format = null); + + /// Вставляет подстрочный фрагмент по индексу. + bool TryInsertSub(int index, string text, RunFormat? format = null); + + /// Вставляет надстрочный фрагмент по индексу. + bool TryInsertSup(int index, string text, RunFormat? format = null); + + /// Применяет заданный формат ко всем существующим фрагментам (поверх их текущего форматирования, заменяя неуказанные свойства). + void ApplyFormatToAllRuns(RunFormat format); + /// Проверяет, входит ли ячейка в объединённый диапазон. bool IsMerged { get; } @@ -699,15 +762,6 @@ public interface ICell /// Устанавливает шрифт ячейки. ICell Set(CellFont format); - /// Устанавливает богатый текст (форматированный) с помощью делегата. - ICell Text(Action value); - - /// Возвращает объект для редактирования богатого текста ячейки (если ячейка содержит InlineString). - bool TryText(out ICellText cellText); - - /// Возвращает объект для редактирования богатого текста ячейки (если ячейка содержит InlineString). - ICellText Text(); - /// Устанавливает простое текстовое значение (без форматирования). ICell Set(string value); @@ -742,76 +796,6 @@ public interface ICell void Clear(); } -/// Представляет богатый текст внутри ячейки (несколько форматированных фрагментов). -public interface ICellText -{ - /// Количество фрагментов (Run) в тексте. - int Count { get; } - - /// Возвращает все фрагменты. - IEnumerable GetRuns(); - - /// Возвращает фрагмент по индексу или null. - IRun? GetRunAt(int index); - - /// Пытается получить фрагмент по индексу. - bool TryGetRunAt(int index, out IRun run); - - /// Первый фрагмент или null. - IRun? First(); - - /// Пытается получить первый фрагмент. - bool TryGetFirst(out IRun run); - - /// Последний фрагмент или null. - IRun? Last(); - - /// Пытается получить последний фрагмент. - bool TryGetLast(out IRun run); - - /// Пытается удалить фрагмент. - bool TryRemoveRun(IRun run); - - /// Удаляет фрагмент по индексу. - bool TryRemoveRun(int index); - - /// Удаляет фрагмент по индексу и возвращает удалённый. - bool TryRemoveRun(int index, out IRun? removed); - - /// Добавляет разрыв строки (перенос внутри ячейки). - ICellText Break(); - - /// Добавляет обычный текстовый фрагмент. - ICellText Run(string text, RunFormat? format = null); - - /// Добавляет фрагмент с последующим разрывом строки. - ICellText RunBreak(string text, RunFormat? format = null); - - /// Добавляет подстрочный фрагмент (эквивалентно AddRun с Vertical = Subscript). - ICellText Sub(string text, RunFormat? format = null); - - /// Добавляет надстрочный фрагмент. - ICellText Sup(string text, RunFormat? format = null); - - /// Вставляет фрагмент по индексу. - bool TryInsertRun(int index, string text, RunFormat? format = null); - - /// Вставляет фрагмент с последующим разрывом строки по индексу. - bool TryInsertRunBreak(int index, string text, RunFormat? format = null); - - /// Вставляет подстрочный фрагмент по индексу. - bool TryInsertSub(int index, string text, RunFormat? format = null); - - /// Вставляет надстрочный фрагмент по индексу. - bool TryInsertSup(int index, string text, RunFormat? format = null); - - /// Применяет заданный формат ко всем существующим фрагментам (поверх их текущего форматирования, заменяя неуказанные свойства). - void ApplyFormatToAllRuns(RunFormat format); - - /// Удаляет все фрагменты, очищая текст ячейки. - void Clear(); -} - /// Представляет один форматированный фрагмент текста внутри ячейки. public interface IRun {