Files
QWERTYkez.OpenXmlProcessors/QWERTYkez.ExcelProcessor/Editors/ExcelCell.cs
melekhin c9ef2a796e
All checks were successful
Publish NuGet packages / publish (push) Successful in 27s
Remove ICellText
2026-06-17 09:33:21 +07:00

1039 lines
33 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
namespace QWERTYkez.ExcelProcessor;
/// <summary>
/// Внутренняя реализация <see cref="ICell"/>.
/// </summary>
internal sealed class ExcelCell(ExcelWriter writer, ExcelSheet sheet, uint row, uint col) : ICell
{
// Кэш фрагментов богатого текста (только для InlineString)
private List<IRun>? _runsCache;
private bool _cacheValid;
// ---- Реализация 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)
{
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<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()
{
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))
{
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);
}
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)
{
// Вырезать-вставить: копируем данные в новую позицию, очищаем старую
var srcCell = GetCellElement();
if (srcCell != null)
{
var cloned = (Cell)srcCell.CloneNode(true);
InsertCellAt(rowIndex, colIndex, cloned);
srcCell.Remove();
}
row = rowIndex;
col = colIndex;
}
return this;
}
public ICell MoveTo(uint rowIndex, string colIndex) => MoveTo(rowIndex, CellAddressHelper.ColumnLetterToIndex(colIndex));
public RowHeight Height
{
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);
}
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;
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;
return writer.GetNumberFormat(styleIndex);
}
/// <inheritdoc />
public CellAlign GetCellAlign()
{
var cell = GetCellElement();
if (cell?.StyleIndex?.Value is not uint styleIndex)
return default;
return writer.GetCellAlign(styleIndex);
}
/// <inheritdoc />
public CellBorder GetCellBorder()
{
var cell = GetCellElement();
if (cell?.StyleIndex?.Value is not uint styleIndex)
return default;
return writer.GetCellBorder(styleIndex);
}
/// <inheritdoc />
public CellFill GetCellFill()
{
var cell = GetCellElement();
if (cell?.StyleIndex?.Value is not uint styleIndex)
return default;
return writer.GetCellFill(styleIndex);
}
/// <inheritdoc />
public CellFont GetCellFont()
{
var cell = GetCellElement();
if (cell?.StyleIndex?.Value is not uint styleIndex)
return default;
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;
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;
writer.ThrowIfDisposed();
lock (writer._syncLock)
{
var cell = GetOrCreateCellElement();
SetNumberFormatInternal(cell, format);
}
return this;
}
/// <inheritdoc />
public ICell Set(CellAlign align)
{
writer.ThrowIfDisposed();
lock (writer._syncLock)
{
var cell = GetOrCreateCellElement();
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)
{
writer.ThrowIfDisposed();
lock (writer._syncLock)
{
var cell = GetOrCreateCellElement();
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)
{
writer.ThrowIfDisposed();
lock (writer._syncLock)
{
var cell = GetOrCreateCellElement();
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)
{
writer.ThrowIfDisposed();
lock (writer._syncLock)
{
var cell = GetOrCreateCellElement();
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)
{
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)
{
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()
{
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)
{
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);
}
else
{
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()
{
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 eRow = GetOrCreateRowElement(sheetData, row);
var cell = FindCellInRow(eRow, col);
if (cell != null) return cell;
cell = new Cell();
string cellRef = CellAddressHelper.ColumnIndexToLetter(col) + row.ToString();
cell.CellReference = cellRef;
InsertCellInRow(eRow, cell, col);
return cell;
}
private static Row? FindRowElement(SheetData sheetData, uint rowIndex)
{
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;
}
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)
{
existing.InsertBeforeSelf(eRow);
inserted = true;
break;
}
}
if (!inserted) sheetData.Append(eRow);
}
private static Cell? FindCellInRow(Row eRow, uint colIndex)
{
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;
}
private static void InsertCellInRow(Row eRow, Cell cell, uint colIndex)
{
string newRef = CellAddressHelper.ColumnIndexToLetter(colIndex) + (eRow.RowIndex?.Value ?? 1).ToString();
cell.CellReference = newRef;
bool inserted = false;
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;
}
}
if (!inserted) eRow.Append(cell);
}
private void InsertCellAt(uint rowIndex, uint colIndex, Cell cell)
{
var sheetData = sheet.GetSheetData();
var row = GetOrCreateRowElement(sheetData, rowIndex);
InsertCellInRow(row, cell, colIndex);
}
private void SetNumberFormatInternal(Cell cell, NumberFormatPattern format)
{
if (format == null) return;
int styleIndex = writer.GetOrCreateCellFormatId(
numberFormat: format,
font: null,
fill: null,
border: null,
align: null
);
cell.StyleIndex = (uint)styleIndex;
}
private string GetSharedString(uint index)
{
return writer.GetSharedString(index);
}
private int GetOrAddSharedString(string value)
{
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() =>
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);
range = new ExcelRange(writer, sheet, rowStart, colStart, rowEnd, colEnd);
return true;
}
}