Files
QWERTYkez.OpenXmlProcessors/QWERTYkez.ExcelProcessor/Editors/ExcelCell.cs

1039 lines
33 KiB
C#
Raw Normal View History

2026-06-08 14:31:31 +07:00
namespace QWERTYkez.ExcelProcessor;
/// <summary>
/// Внутренняя реализация <see cref="ICell"/>.
/// </summary>
2026-06-17 09:33:21 +07:00
internal sealed class ExcelCell(ExcelWriter writer, ExcelSheet sheet, uint row, uint col) : ICell
{
2026-06-17 09:33:21 +07:00
// Кэш фрагментов богатого текста (только для InlineString)
private List<IRun>? _runsCache;
private bool _cacheValid;
2026-06-17 09:33:21 +07:00
// ---- Реализация ICell (новые методы) ----
public int RunsCount
{
get
{
EnsureCacheValid();
return _runsCache?.Count ?? 0;
}
}
public IEnumerable<IRun> GetRuns()
{
EnsureCacheValid();
return _runsCache ?? Enumerable.Empty<IRun>();
}
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)
{
2026-06-17 09:33:21 +07:00
Run(text, format);
Break();
return this;
}
2026-06-17 09:33:21 +07:00
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<Run>())
{
string text = run.GetFirstChild<Text>()?.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
{
var range = GetMergedRange();
return range != null;
}
}
public IRange? GetMergedRange()
{
2026-06-17 09:33:21 +07:00
writer.ThrowIfDisposed();
lock (writer._syncLock)
{
var mergeCells = GetMergeCells();
if (mergeCells == null) return null;
foreach (var mergeCell in mergeCells.Elements<MergeCell>())
{
if (TryParseRangeReference(mergeCell.Reference?.Value ?? string.Empty, out var range))
{
2026-06-17 09:33:21 +07:00
if (row >= range.RowStart && row <= range.RowEnd &&
col >= range.ColStart && col <= range.ColEnd)
return range;
}
}
return null;
}
}
public bool TryGetMergedRange(IRange range)
{
var merged = GetMergedRange();
if (merged == null) return false;
return merged.Equals(range);
}
2026-06-17 09:33:21 +07:00
public uint Row => row;
public uint Col => col;
public string ColLetter => CellAddressHelper.ColumnIndexToLetter(col);
public ICell MoveTo(uint rowIndex, uint colIndex)
{
2026-06-17 09:33:21 +07:00
if (rowIndex == row && colIndex == col) return this;
writer.ThrowIfDisposed();
lock (writer._syncLock)
{
// Вырезать-вставить: копируем данные в новую позицию, очищаем старую
var srcCell = GetCellElement();
if (srcCell != null)
{
var cloned = (Cell)srcCell.CloneNode(true);
InsertCellAt(rowIndex, colIndex, cloned);
srcCell.Remove();
}
2026-06-17 09:33:21 +07:00
row = rowIndex;
col = colIndex;
}
return this;
}
public ICell MoveTo(uint rowIndex, string colIndex) => MoveTo(rowIndex, CellAddressHelper.ColumnLetterToIndex(colIndex));
public RowHeight Height
{
2026-06-17 09:33:21 +07:00
get => ExcelRow.GetHeight(writer, sheet, row);
set => ExcelRow.SetHeight(writer, sheet, row, value);
}
public ColumnWidth Width
{
2026-06-17 09:33:21 +07:00
get => ExcelColumn.GetWidth(writer, sheet, col);
set => ExcelColumn.SetWidth(writer, sheet, col, value);
}
public bool IsNumber
{
get
{
var cell = GetCellElement();
if (cell == null) return false;
if (cell.DataType != null && cell.DataType.Value == CellValues.Number) return true;
// Если тип не указан, но значение число Excel считает числом
if (cell.DataType == null && cell.CellValue != null && double.TryParse(cell.CellValue.Text, out _)) return true;
return false;
}
}
public bool IsBoolean
{
get
{
var cell = GetCellElement();
return cell != null && cell.DataType != null && cell.DataType.Value == CellValues.Boolean;
}
}
public bool IsError
{
get
{
var cell = GetCellElement();
return cell != null && cell.DataType != null && cell.DataType.Value == CellValues.Error;
}
}
public bool IsDate
{
get
{
if (!IsNumber) return false;
var format = GetNumberFormat();
if (format == null) return false;
// Встроенный формат: проверяем по ID
if (format.Id.HasValue && format.Id.Value < 164)
{
int id = format.Id.Value;
return (id >= 14 && id <= 22) || // даты
(id >= 27 && id <= 36) || // время и дата-время
(id >= 45 && id <= 47); // другие форматы времени
}
else
{
// Пользовательский формат: ищем символы даты/времени
string code = format.Format?.ToLowerInvariant() ?? string.Empty;
bool isDate = code.Contains('d') && code.Contains('m') && code.Contains('y');
bool isTime = code.Contains('h') && code.Contains('m') && code.Contains('s');
return isDate || isTime;
}
}
}
public bool IsLocked
{
get
{
var cell = GetCellElement();
if (cell?.StyleIndex?.Value is not uint styleIndex)
return false;
2026-06-17 09:33:21 +07:00
return writer.IsCellLocked(styleIndex);
}
}
public bool HasFormula => GetCellElement()?.CellFormula != null;
public string GetString()
{
var cell = GetCellElement();
if (cell == null) return string.Empty;
if (cell.DataType != null && cell.DataType.Value == CellValues.SharedString)
{
// Общая таблица строк
if (uint.TryParse(cell.CellValue?.Text, out uint idx))
return GetSharedString(idx);
return string.Empty;
}
if (cell.DataType != null && cell.DataType.Value == CellValues.InlineString)
{
// Rich text извлекаем весь текст
return ExtractTextFromInlineString(cell.InlineString);
}
if (cell.DataType != null && cell.DataType.Value == CellValues.Boolean)
return cell.CellValue?.Text ?? "FALSE";
if (cell.DataType != null && cell.DataType.Value == CellValues.Error)
return cell.CellValue?.Text ?? "#NULL!";
// Число или общий тип
return cell.CellValue?.Text ?? string.Empty;
}
/// <inheritdoc />
public NumberFormatPattern? GetNumberFormat()
{
var cell = GetCellElement();
if (cell?.StyleIndex?.Value is not uint styleIndex)
return null;
2026-06-17 09:33:21 +07:00
return writer.GetNumberFormat(styleIndex);
}
/// <inheritdoc />
public CellAlign GetCellAlign()
{
var cell = GetCellElement();
if (cell?.StyleIndex?.Value is not uint styleIndex)
return default;
2026-06-17 09:33:21 +07:00
return writer.GetCellAlign(styleIndex);
}
/// <inheritdoc />
public CellBorder GetCellBorder()
{
var cell = GetCellElement();
if (cell?.StyleIndex?.Value is not uint styleIndex)
return default;
2026-06-17 09:33:21 +07:00
return writer.GetCellBorder(styleIndex);
}
/// <inheritdoc />
public CellFill GetCellFill()
{
var cell = GetCellElement();
if (cell?.StyleIndex?.Value is not uint styleIndex)
return default;
2026-06-17 09:33:21 +07:00
return writer.GetCellFill(styleIndex);
}
/// <inheritdoc />
public CellFont GetCellFont()
{
var cell = GetCellElement();
if (cell?.StyleIndex?.Value is not uint styleIndex)
return default;
2026-06-17 09:33:21 +07:00
return writer.GetCellFont(styleIndex);
}
public bool TryGetBoolean(out bool value)
{
value = false;
var cell = GetCellElement();
if (cell == null) return false;
if (cell.DataType != null && cell.DataType.Value == CellValues.Boolean)
{
string txt = cell.CellValue?.Text ?? "0";
value = txt == "1" || txt.Equals("true", StringComparison.OrdinalIgnoreCase);
return true;
}
return false;
}
public bool? GetBoolean() => TryGetBoolean(out bool v) ? v : null;
public bool TryGetDate(out DateTime value)
{
value = default;
if (!IsNumber) return false;
if (TryGetNumber(out double num))
{
// Excel даты: 1 января 1900 = 1, 1 января 1904 = 0 в 1904 системе
// Предполагаем 1900 систему
value = DateTime.FromOADate(num);
return true;
}
return false;
}
public DateTime? TryGetDate() => TryGetDate(out DateTime d) ? d : null;
public bool TryGetNumber(out double value)
{
value = 0;
var cell = GetCellElement();
if (cell == null) return false;
if (cell.DataType != null && cell.DataType.Value == CellValues.Number)
{
if (cell.CellValue != null && double.TryParse(cell.CellValue.Text, NumberStyles.Any, CultureInfo.InvariantCulture, out value))
return true;
}
else if (cell.DataType == null && cell.CellValue != null && double.TryParse(cell.CellValue.Text, NumberStyles.Any, CultureInfo.InvariantCulture, out value))
return true;
return false;
}
public double? TryGetNumber() => TryGetNumber(out double v) ? v : null;
public bool TrySet(string formula, NumberFormatPattern? format = null)
{
if (string.IsNullOrEmpty(formula)) return false;
2026-06-17 09:33:21 +07:00
writer.ThrowIfDisposed();
lock (writer._syncLock)
{
var cell = GetOrCreateCellElement();
cell.CellFormula = new CellFormula(formula);
cell.DataType = null; // формула сама определяет тип
if (format != null)
SetNumberFormatInternal(cell, format);
return true;
}
}
public ICell Set(string formula, NumberFormatPattern? format = null)
{
if (!TrySet(formula, format))
throw new InvalidOperationException("Failed to set formula");
return this;
}
public ICell Set(NumberFormatPattern format)
{
if (format == null) return this;
2026-06-17 09:33:21 +07:00
writer.ThrowIfDisposed();
lock (writer._syncLock)
{
var cell = GetOrCreateCellElement();
SetNumberFormatInternal(cell, format);
}
return this;
}
/// <inheritdoc />
public ICell Set(CellAlign align)
{
2026-06-17 09:33:21 +07:00
writer.ThrowIfDisposed();
lock (writer._syncLock)
{
var cell = GetOrCreateCellElement();
2026-06-17 09:33:21 +07:00
int styleIndex = writer.GetOrCreateCellFormatId(
numberFormat: null,
font: null,
fill: null,
border: null,
align: align
);
cell.StyleIndex = (uint)styleIndex;
return this;
}
}
/// <inheritdoc />
public ICell Set(CellBorder border)
{
2026-06-17 09:33:21 +07:00
writer.ThrowIfDisposed();
lock (writer._syncLock)
{
var cell = GetOrCreateCellElement();
2026-06-17 09:33:21 +07:00
int styleIndex = writer.GetOrCreateCellFormatId(
numberFormat: null,
font: null,
fill: null,
border: border,
align: null
);
cell.StyleIndex = (uint)styleIndex;
return this;
}
}
/// <inheritdoc />
public ICell Set(CellFill fill)
{
2026-06-17 09:33:21 +07:00
writer.ThrowIfDisposed();
lock (writer._syncLock)
{
var cell = GetOrCreateCellElement();
2026-06-17 09:33:21 +07:00
int styleIndex = writer.GetOrCreateCellFormatId(
numberFormat: null,
font: null,
fill: fill,
border: null,
align: null
);
cell.StyleIndex = (uint)styleIndex;
return this;
}
}
/// <inheritdoc />
public ICell Set(CellFont font)
{
2026-06-17 09:33:21 +07:00
writer.ThrowIfDisposed();
lock (writer._syncLock)
{
var cell = GetOrCreateCellElement();
2026-06-17 09:33:21 +07:00
int styleIndex = writer.GetOrCreateCellFormatId(
numberFormat: null,
font: font,
fill: null,
border: null,
align: null
);
cell.StyleIndex = (uint)styleIndex;
return this;
}
}
public ICell Set(bool value)
{
2026-06-17 09:33:21 +07:00
writer.ThrowIfDisposed();
lock (writer._syncLock)
{
var cell = GetOrCreateCellElement();
cell.DataType = CellValues.Boolean;
cell.CellValue = new CellValue(value ? "1" : "0");
cell.InlineString = null;
cell.CellFormula = null;
}
return this;
}
public ICell Set(DateTime value, NumberFormatPattern? format = null)
{
double oa = value.ToOADate();
Set(oa, format);
return this;
}
public ICell Set(decimal value, NumberFormatPattern? format = null) => Set((double)value, format);
public ICell Set(double value, NumberFormatPattern? format = null)
{
2026-06-17 09:33:21 +07:00
writer.ThrowIfDisposed();
lock (writer._syncLock)
{
var cell = GetOrCreateCellElement();
cell.DataType = CellValues.Number;
cell.CellValue = new CellValue(value.ToString(CultureInfo.InvariantCulture));
if (format != null)
SetNumberFormatInternal(cell, format);
}
return this;
}
public ICell Set(float value, NumberFormatPattern? format = null) => Set((double)value, format);
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 ClearFormat()
{
2026-06-17 09:33:21 +07:00
writer.ThrowIfDisposed();
lock (writer._syncLock)
{
var cell = GetCellElement();
cell?.StyleIndex = null;
}
}
public void Clear()
{
ClearContent();
ClearFormat();
}
public ICell CopyTo(uint rowIndex, uint colIndex, out ICell copiedCell)
{
2026-06-17 09:33:21 +07:00
writer.ThrowIfDisposed();
lock (writer._syncLock)
{
var srcCell = GetCellElement();
if (srcCell != null)
{
var cloned = (Cell)srcCell.CloneNode(true);
InsertCellAt(rowIndex, colIndex, cloned);
2026-06-17 09:33:21 +07:00
copiedCell = new ExcelCell(writer, sheet, rowIndex, colIndex);
}
else
{
2026-06-17 09:33:21 +07:00
copiedCell = new ExcelCell(writer, sheet, rowIndex, colIndex);
}
return this;
}
}
public ICell CopyTo(uint rowIndex, string colIndex, out ICell copiedCell) =>
CopyTo(rowIndex, CellAddressHelper.ColumnLetterToIndex(colIndex), out copiedCell);
// Private helpers
private Cell? GetCellElement()
{
2026-06-17 09:33:21 +07:00
var sheetData = sheet.GetSheetData();
var eRow = FindRowElement(sheetData, row);
if (eRow == null) return null;
return FindCellInRow(eRow, col);
}
private Cell GetOrCreateCellElement()
{
2026-06-17 09:33:21 +07:00
var sheetData = sheet.GetSheetData();
var eRow = GetOrCreateRowElement(sheetData, row);
var cell = FindCellInRow(eRow, col);
if (cell != null) return cell;
cell = new Cell();
2026-06-17 09:33:21 +07:00
string cellRef = CellAddressHelper.ColumnIndexToLetter(col) + row.ToString();
cell.CellReference = cellRef;
2026-06-17 09:33:21 +07:00
InsertCellInRow(eRow, cell, col);
return cell;
}
private static Row? FindRowElement(SheetData sheetData, uint rowIndex)
{
2026-06-17 09:33:21 +07:00
foreach (var eRow in sheetData.Elements<Row>())
if (eRow.RowIndex?.Value == rowIndex) return eRow;
return null;
}
private static Row GetOrCreateRowElement(SheetData sheetData, uint rowIndex)
{
var existing = FindRowElement(sheetData, rowIndex);
if (existing != null) return existing;
var newRow = new Row { RowIndex = rowIndex };
InsertRowElement(sheetData, newRow, rowIndex);
return newRow;
}
2026-06-17 09:33:21 +07:00
private static void InsertRowElement(SheetData sheetData, Row eRow, uint rowIndex)
{
bool inserted = false;
foreach (var existing in sheetData.Elements<Row>().ToList())
{
if (existing.RowIndex?.Value > rowIndex)
{
2026-06-17 09:33:21 +07:00
existing.InsertBeforeSelf(eRow);
inserted = true;
break;
}
}
2026-06-17 09:33:21 +07:00
if (!inserted) sheetData.Append(eRow);
}
2026-06-17 09:33:21 +07:00
private static Cell? FindCellInRow(Row eRow, uint colIndex)
{
2026-06-17 09:33:21 +07:00
foreach (var cell in eRow.Elements<Cell>())
{
if (CellAddressHelper.TryParseCellReference(cell.CellReference?.Value ?? string.Empty, out _, out uint col) && col == colIndex)
return cell;
}
return null;
}
2026-06-17 09:33:21 +07:00
private static void InsertCellInRow(Row eRow, Cell cell, uint colIndex)
{
2026-06-17 09:33:21 +07:00
string newRef = CellAddressHelper.ColumnIndexToLetter(colIndex) + (eRow.RowIndex?.Value ?? 1).ToString();
cell.CellReference = newRef;
bool inserted = false;
2026-06-17 09:33:21 +07:00
foreach (var existing in eRow.Elements<Cell>().ToList())
{
if (CellAddressHelper.TryParseCellReference(existing.CellReference?.Value ?? string.Empty, out _, out uint existingCol) && existingCol > colIndex)
{
existing.InsertBeforeSelf(cell);
inserted = true;
break;
}
}
2026-06-17 09:33:21 +07:00
if (!inserted) eRow.Append(cell);
}
private void InsertCellAt(uint rowIndex, uint colIndex, Cell cell)
{
2026-06-17 09:33:21 +07:00
var sheetData = sheet.GetSheetData();
var row = GetOrCreateRowElement(sheetData, rowIndex);
InsertCellInRow(row, cell, colIndex);
}
private void SetNumberFormatInternal(Cell cell, NumberFormatPattern format)
{
if (format == null) return;
2026-06-17 09:33:21 +07:00
int styleIndex = writer.GetOrCreateCellFormatId(
numberFormat: format,
font: null,
fill: null,
border: null,
align: null
);
cell.StyleIndex = (uint)styleIndex;
}
private string GetSharedString(uint index)
{
2026-06-17 09:33:21 +07:00
return writer.GetSharedString(index);
}
private int GetOrAddSharedString(string value)
{
2026-06-17 09:33:21 +07:00
return writer.GetOrAddSharedString(value);
}
private string ExtractTextFromInlineString(InlineString? inlineString)
{
if (inlineString == null) return string.Empty;
var sb = new System.Text.StringBuilder();
foreach (var run in inlineString.Elements<Run>())
{
var text = run.GetFirstChild<Text>()?.Text ?? string.Empty;
sb.Append(text);
}
return sb.ToString();
}
private MergeCells? GetMergeCells() =>
2026-06-17 09:33:21 +07:00
sheet.Worksheet.GetFirstChild<MergeCells>();
private bool TryParseRangeReference(string reference, out ExcelRange range)
{
range = null!;
if (string.IsNullOrEmpty(reference)) return false;
string[] parts = reference.Split(':');
if (parts.Length != 2) return false;
if (!CellAddressHelper.TryParseCellReference(parts[0] + "1", out uint row1, out uint col1)) return false;
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);
2026-06-17 09:33:21 +07:00
range = new ExcelRange(writer, sheet, rowStart, colStart, rowEnd, colEnd);
return true;
}
}