namespace QWERTYkez.ExcelProcessor; /// /// Внутренняя реализация . /// internal sealed class ExcelCell(ExcelWriter writer, ExcelSheet sheet, uint row, uint col) : ICell { // Кэш фрагментов богатого текста (только для InlineString) private List? _runsCache; private bool _cacheValid; // ---- Реализация ICell (новые методы) ---- public int RunsCount { 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 { 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()) { 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; } /// public NumberFormatPattern? GetNumberFormat() { var cell = GetCellElement(); if (cell?.StyleIndex?.Value is not uint styleIndex) return null; return writer.GetNumberFormat(styleIndex); } /// public CellAlign GetCellAlign() { var cell = GetCellElement(); if (cell?.StyleIndex?.Value is not uint styleIndex) return default; return writer.GetCellAlign(styleIndex); } /// public CellBorder GetCellBorder() { var cell = GetCellElement(); if (cell?.StyleIndex?.Value is not uint styleIndex) return default; return writer.GetCellBorder(styleIndex); } /// public CellFill GetCellFill() { var cell = GetCellElement(); if (cell?.StyleIndex?.Value is not uint styleIndex) return default; return writer.GetCellFill(styleIndex); } /// 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; } /// 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; } } /// 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; } } /// 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; } } /// 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()) 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().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()) { 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().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()) { var text = run.GetFirstChild()?.Text ?? string.Empty; sb.Append(text); } return sb.ToString(); } private MergeCells? GetMergeCells() => sheet.Worksheet.GetFirstChild(); 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; } }