Files
QWERTYkez.OpenXmlProcessors/QWERTYkez.ExcelProcessor/Editors/ExcelRange.cs
2026-06-05 15:58:03 +07:00

886 lines
29 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.Editors;
/// <summary>
/// Внутренняя реализация <see cref="IRange"/>.
/// </summary>
internal sealed class ExcelRange : IRange
{
private readonly ExcelWriter _writer;
private readonly ExcelSheet _sheet;
private uint _rowStart, _rowEnd;
private uint _colStart, _colEnd;
internal ExcelRange(ExcelWriter writer, ExcelSheet sheet, uint rowStart, uint colStart, uint rowEnd, uint colEnd)
{
_writer = writer;
_sheet = sheet;
_rowStart = rowStart;
_rowEnd = rowEnd;
_colStart = colStart;
_colEnd = colEnd;
}
public uint RowStart => _rowStart;
public uint RowEnd => _rowEnd;
public uint ColStart => _colStart;
public uint ColEnd => _colEnd;
public string ColStartLetter => CellAddressHelper.ColumnIndexToLetter(_colStart);
public string ColEndLetter => CellAddressHelper.ColumnIndexToLetter(_colEnd);
public uint Rows => _rowEnd - _rowStart + 1;
public uint Cols => _colEnd - _colStart + 1;
public bool IsMerged
{
get
{
_writer.ThrowIfDisposed();
lock (_writer._syncLock)
{
var mergeCells = GetMergeCells();
if (mergeCells == null) return false;
foreach (var mergeCell in mergeCells.Elements<MergeCell>())
{
if (TryParseRangeReference(mergeCell.Reference?.Value ?? string.Empty, out var refRange))
{
if (refRange._rowStart == _rowStart && refRange._rowEnd == _rowEnd &&
refRange._colStart == _colStart && refRange._colEnd == _colEnd)
return true;
}
}
return false;
}
}
}
/// <summary>
/// Перемещает текущий диапазон в новую позицию (как "вырезать-вставить").
/// Исходный диапазон очищается, а текущий объект IRange перемещается на новое место.
/// </summary>
/// <param name="newRow">Номер строки для нового верхнего левого угла.</param>
/// <param name="newCol">Номер столбца для нового верхнего левого угла.</param>
/// <returns>Тот же объект IRange с новыми координатами.</returns>
public IRange MoveTo(uint newRow, uint newCol)
{
if (newRow == _rowStart && newCol == _colStart) return this;
_writer.ThrowIfDisposed();
lock (_writer._syncLock)
{
uint newRowEnd = newRow + (_rowEnd - _rowStart);
uint newColEnd = newCol + (_colEnd - _colStart);
bool overlap = RangesOverlap(_rowStart, _colStart, _rowEnd, _colEnd, newRow, newCol, newRowEnd, newColEnd);
bool horizontalShift = (newRow == _rowStart);
bool verticalShift = (newCol == _colStart);
if (!overlap)
{
CopyCells(_rowStart, _colStart, _rowEnd, _colEnd, newRow, newCol, CopyOrder.Any);
ClearRangeData(_rowStart, _colStart, _rowEnd, _colEnd);
}
else if (horizontalShift)
{
CopyOrder order = (newCol > _colStart) ? CopyOrder.RightToLeft : CopyOrder.LeftToRight;
CopyCells(_rowStart, _colStart, _rowEnd, _colEnd, newRow, newCol, order);
ClearRangeData(_rowStart, _colStart, _rowEnd, _colEnd);
}
else if (verticalShift)
{
CopyOrder order = (newRow > _rowStart) ? CopyOrder.BottomToTop : CopyOrder.TopToBottom;
CopyCells(_rowStart, _colStart, _rowEnd, _colEnd, newRow, newCol, order);
ClearRangeData(_rowStart, _colStart, _rowEnd, _colEnd);
}
else
{
// Диагональное перемещение два последовательных сдвига
MoveTo(_rowStart, newCol);
MoveTo(newRow, _colStart);
return this;
}
_rowStart = newRow;
_rowEnd = newRowEnd;
_colStart = newCol;
_colEnd = newColEnd;
return this;
}
}
public IRange MoveTo(uint rowIndex, string colIndex)
{
return MoveTo(rowIndex, CellAddressHelper.ColumnLetterToIndex(colIndex));
}
private enum CopyOrder
{
Any,
LeftToRight,
RightToLeft,
TopToBottom,
BottomToTop
}
/// <summary>Копирует ячейки из исходного диапазона в целевой, поддерживая различные порядки обхода.</summary>
private void CopyCells(uint srcRowStart, uint srcColStart, uint srcRowEnd, uint srcColEnd,
uint dstRowStart, uint dstColStart, CopyOrder order)
{
// Определяем все строки, которые могут понадобиться (исходные и целевые)
var rowIndices = new HashSet<uint>();
for (uint r = srcRowStart; r <= srcRowEnd; r++) rowIndices.Add(r);
uint dstRowEnd = dstRowStart + (srcRowEnd - srcRowStart);
for (uint r = dstRowStart; r <= dstRowEnd; r++) rowIndices.Add(r);
var rowsDict = GetRowDictionary(rowIndices);
int rowStep = 1, colStep = 1;
int rowStart = (int)srcRowStart, rowEnd = (int)srcRowEnd;
int colStart = (int)srcColStart, colEnd = (int)srcColEnd;
if (order == CopyOrder.BottomToTop)
{
rowStep = -1;
rowStart = (int)srcRowEnd;
rowEnd = (int)srcRowStart;
}
if (order == CopyOrder.RightToLeft)
{
colStep = -1;
colStart = (int)srcColEnd;
colEnd = (int)srcColStart;
}
for (int r = rowStart; (rowStep > 0 ? r <= rowEnd : r >= rowEnd); r += rowStep)
{
if (!rowsDict.TryGetValue((uint)r, out var srcRow))
continue;
for (int c = colStart; (colStep > 0 ? c <= colEnd : c >= colEnd); c += colStep)
{
Cell? srcCell = FindCellInRowFast(srcRow, (uint)c);
if (srcCell == null) continue;
Cell cloned = (Cell)srcCell.CloneNode(true);
uint dstRow = dstRowStart + (uint)(r - srcRowStart);
uint dstCol = dstColStart + (uint)(c - srcColStart);
cloned.CellReference = $"{CellAddressHelper.ColumnIndexToLetter(dstCol)}{dstRow}";
InsertCellAt(dstRow, dstCol, cloned);
}
}
}
/// <summary>Строит словарь строк для указанных индексов строк.</summary>
private Dictionary<uint, Row> GetRowDictionary(HashSet<uint> rowIndices)
{
var dict = new Dictionary<uint, Row>();
var sheetData = _sheet.GetSheetData();
foreach (var row in sheetData.Elements<Row>())
{
if (row.RowIndex?.Value is uint idx && rowIndices.Contains(idx))
dict[idx] = row;
}
return dict;
}
/// <summary>Быстрый поиск ячейки в строке (линейный, подходит для типичного количества ячеек в строке).</summary>
private Cell? FindCellInRowFast(Row row, uint colIndex)
{
foreach (var cell in row.Elements<Cell>())
{
if (CellAddressHelper.TryParseCellReference(cell.CellReference?.Value ?? string.Empty, out _, out uint col) && col == colIndex)
return cell;
}
return null;
}
/// <summary>Проверяет, пересекаются ли два прямоугольных диапазона.</summary>
private static bool RangesOverlap(uint r1s, uint c1s, uint r1e, uint c1e,
uint r2s, uint c2s, uint r2e, uint c2e)
{
return !(r1e < r2s || r2e < r1s || c1e < c2s || c2e < c1s);
}
/// <summary>Вставляет ячейку в указанную позицию, предварительно удаляя существующую.</summary>
private void InsertCellAt(uint row, uint col, Cell cell)
{
DeleteCellAt(row, col);
var sheetData = _sheet.GetSheetData();
var rowElement = GetOrCreateRowElement(sheetData, row);
InsertCellInRow(rowElement, cell, col);
}
/// <summary>Удаляет ячейку, если она существует.</summary>
private void DeleteCellAt(uint row, uint col)
{
var sheetData = _sheet.GetSheetData();
var rowElement = FindRowElement(sheetData, row);
if (rowElement == null) return;
var cell = FindCellInRow(rowElement, col);
cell?.Remove();
}
/// <summary>Получает или создаёт строку с указанным индексом.</summary>
private 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;
}
/// <summary>Вставляет ячейку в строку с сохранением порядка столбцов.</summary>
private void InsertCellInRow(Row row, Cell cell, uint colIndex)
{
string newRef = $"{CellAddressHelper.ColumnIndexToLetter(colIndex)}{row.RowIndex?.Value ?? 1}";
cell.CellReference = newRef;
bool inserted = false;
foreach (var existing in row.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) row.Append(cell);
}
/// <summary>Очищает данные (содержимое) в указанном диапазоне.</summary>
private void ClearRangeData(uint rowStart, uint colStart, uint rowEnd, uint colEnd)
{
for (uint r = rowStart; r <= rowEnd; r++)
{
var row = FindRowElement(_sheet.GetSheetData(), r);
if (row == null) continue;
for (uint c = colStart; c <= colEnd; c++)
{
var cell = FindCellInRow(row, c);
cell?.Remove();
}
}
}
public bool TryMerge()
{
_writer.ThrowIfDisposed();
lock (_writer._syncLock)
{
if (IsMerged) return false;
var worksheet = _sheet.Worksheet;
var mergeCells = worksheet.GetFirstChild<MergeCells>();
if (mergeCells == null)
{
mergeCells = new MergeCells();
worksheet.Append(mergeCells);
}
string refStr = $"{ColStartLetter}{RowStart}:{ColEndLetter}{RowEnd}";
mergeCells.Append(new MergeCell { Reference = refStr });
return true;
}
}
public void Unmerge()
{
_writer.ThrowIfDisposed();
lock (_writer._syncLock)
{
var mergeCells = GetMergeCells();
if (mergeCells == null) return;
var toRemove = mergeCells.Elements<MergeCell>()
.FirstOrDefault(mc => TryParseRangeReference(mc.Reference?.Value ?? string.Empty, out var rng) &&
rng._rowStart == _rowStart && rng._rowEnd == _rowEnd &&
rng._colStart == _colStart && rng._colEnd == _colEnd);
toRemove?.Remove();
}
}
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 refRange))
{
if (refRange._rowStart >= _rowStart && refRange._rowEnd <= _rowEnd &&
refRange._colStart >= _colStart && refRange._colEnd <= _colEnd)
return refRange;
}
}
return null;
}
}
public bool TryGetMergedRange(IRange range)
{
var merged = GetMergedRange();
if (merged == null) return false;
return merged.Equals(range);
}
public IRange SetNumberFormat(NumberFormatPattern format)
{
if (format == null) return this;
_writer.ThrowIfDisposed();
lock (_writer._syncLock)
{
int fmtId = GetOrCreateNumberFormatId(format);
for (uint r = _rowStart; r <= _rowEnd; r++)
{
for (uint c = _colStart; c <= _colEnd; c++)
{
var cell = GetCellInternal(r, c);
cell?.StyleIndex = (uint)fmtId;
}
}
}
return this;
}
/// <inheritdoc />
public IRange SetCellAlign(CellAlign align)
{
_writer.ThrowIfDisposed();
lock (_writer._syncLock)
{
int styleIndex = _writer.GetOrCreateCellFormatId(
numberFormat: null,
font: null,
fill: null,
border: null,
align: align
);
ApplyStyleToRange((uint)styleIndex);
return this;
}
}
/// <inheritdoc />
public IRange SetCellBorder(CellBorder border)
{
_writer.ThrowIfDisposed();
lock (_writer._syncLock)
{
int styleIndex = _writer.GetOrCreateCellFormatId(
numberFormat: null,
font: null,
fill: null,
border: border,
align: null
);
ApplyStyleToRange((uint)styleIndex);
return this;
}
}
/// <inheritdoc />
public IRange SetCellFill(CellFill fill)
{
_writer.ThrowIfDisposed();
lock (_writer._syncLock)
{
int styleIndex = _writer.GetOrCreateCellFormatId(
numberFormat: null,
font: null,
fill: fill,
border: null,
align: null
);
ApplyStyleToRange((uint)styleIndex);
return this;
}
}
/// <inheritdoc />
public IRange SetCellFont(CellFont font)
{
_writer.ThrowIfDisposed();
lock (_writer._syncLock)
{
int styleIndex = _writer.GetOrCreateCellFormatId(
numberFormat: null,
font: font,
fill: null,
border: null,
align: null
);
ApplyStyleToRange((uint)styleIndex);
return this;
}
}
/// <summary>
/// Применяет стиль ко всем ячейкам диапазона.
/// </summary>
/// <param name="styleIndex">Индекс стиля для применения.</param>
/// <param name="createIfMissing">Если true, создаёт недостающие ячейки (по умолчанию true).</param>
private void ApplyStyleToRange(uint styleIndex, bool createIfMissing = true)
{
for (uint row = _rowStart; row <= _rowEnd; row++)
{
for (uint col = _colStart; col <= _colEnd; col++)
{
var cell = GetCellInternal(row, col);
if (cell != null)
{
cell.StyleIndex = styleIndex;
}
else if (createIfMissing)
{
var newCell = new Cell
{
CellReference = $"{CellAddressHelper.ColumnIndexToLetter(col)}{row}",
StyleIndex = styleIndex
};
InsertCellAt(row, col, newCell);
}
}
}
}
public IEnumerable<ICell> Cells
{
get
{
for (uint r = _rowStart; r <= _rowEnd; r++)
for (uint c = _colStart; c <= _colEnd; c++)
yield return new ExcelCell(_writer, _sheet, r, c);
}
}
public bool GetSubCell(uint row, uint col, out ICell cell)
{
uint globalRow = _rowStart + row - 1;
uint globalCol = _colStart + col - 1;
if (globalRow > _rowEnd || globalCol > _colEnd)
{
cell = default!;
return false;
}
cell = new ExcelCell(_writer, _sheet, globalRow, globalCol);
return true;
}
public bool GetSubCell(uint row, string col, out ICell cell)
{
uint colIndex = CellAddressHelper.ColumnLetterToIndex(col);
return GetSubCell(row, colIndex, out cell);
}
public bool TryEditSubCell(uint row, uint col, Action<ICell> edit)
{
if (GetSubCell(row, col, out var cell))
{
edit(cell);
return true;
}
return false;
}
public bool TryEditSubCell(uint row, uint col, string value)
{
if (GetSubCell(row, col, out var cell))
{
cell.Set(value);
return true;
}
return false;
}
public bool TryEditSubCell(uint row, uint col, bool value)
{
if (GetSubCell(row, col, out var cell))
{
cell.Set(value);
return true;
}
return false;
}
public bool TryEditSubCell(uint row, uint col, string formula, NumberFormatPattern? format = null)
{
if (GetSubCell(row, col, out var cell))
{
cell.Set(formula, format);
return true;
}
return false;
}
public bool TryEditSubCell(uint row, uint col, DateTime value, NumberFormatPattern? format = null)
{
if (GetSubCell(row, col, out var cell))
{
cell.Set(value, format);
return true;
}
return false;
}
public bool TryEditSubCell(uint row, uint col, decimal value, NumberFormatPattern? format = null)
{
if (GetSubCell(row, col, out var cell))
{
cell.Set(value, format);
return true;
}
return false;
}
public bool TryEditSubCell(uint row, uint col, double value, NumberFormatPattern? format = null)
{
if (GetSubCell(row, col, out var cell))
{
cell.Set(value, format);
return true;
}
return false;
}
public bool TryEditSubCell(uint row, uint col, float value, NumberFormatPattern? format = null)
{
if (GetSubCell(row, col, out var cell))
{
cell.Set(value, format);
return true;
}
return false;
}
public bool TryEditSubCell(uint row, uint col, int value, NumberFormatPattern? format = null)
{
if (GetSubCell(row, col, out var cell))
{
cell.Set(value, format);
return true;
}
return false;
}
public bool TryEditSubCell(uint row, uint col, long value, NumberFormatPattern? format = null)
{
if (GetSubCell(row, col, out var cell))
{
cell.Set(value, format);
return true;
}
return false;
}
public bool TryEditSubCell(uint row, string col, Action<ICell> edit)
{
if (GetSubCell(row, col, out var cell))
{
edit(cell);
return true;
}
return false;
}
public bool TryEditSubCell(uint row, string col, string value)
{
if (GetSubCell(row, col, out var cell))
{
cell.Set(value);
return true;
}
return false;
}
public bool TryEditSubCell(uint row, string col, bool value)
{
if (GetSubCell(row, col, out var cell))
{
cell.Set(value);
return true;
}
return false;
}
public bool TryEditSubCell(uint row, string col, string formula, NumberFormatPattern? format = null)
{
if (GetSubCell(row, col, out var cell))
{
cell.Set(formula, format);
return true;
}
return false;
}
public bool TryEditSubCell(uint row, string col, DateTime value, NumberFormatPattern? format = null)
{
if (GetSubCell(row, col, out var cell))
{
cell.Set(value, format);
return true;
}
return false;
}
public bool TryEditSubCell(uint row, string col, decimal value, NumberFormatPattern? format = null)
{
if (GetSubCell(row, col, out var cell))
{
cell.Set(value, format);
return true;
}
return false;
}
public bool TryEditSubCell(uint row, string col, double value, NumberFormatPattern? format = null)
{
if (GetSubCell(row, col, out var cell))
{
cell.Set(value, format);
return true;
}
return false;
}
public bool TryEditSubCell(uint row, string col, float value, NumberFormatPattern? format = null)
{
if (GetSubCell(row, col, out var cell))
{
cell.Set(value, format);
return true;
}
return false;
}
public bool TryEditSubCell(uint row, string col, int value, NumberFormatPattern? format = null)
{
if (GetSubCell(row, col, out var cell))
{
cell.Set(value, format);
return true;
}
return false;
}
public bool TryEditSubCell(uint row, string col, long value, NumberFormatPattern? format = null)
{
if (GetSubCell(row, col, out var cell))
{
cell.Set(value, format);
return true;
}
return false;
}
public void ClearContents()
{
ClearRangeData(_rowStart, _colStart, _rowEnd, _colEnd);
}
public void ClearFormats()
{
for (uint r = _rowStart; r <= _rowEnd; r++)
for (uint c = _colStart; c <= _colEnd; c++)
{
var cell = GetCellInternal(r, c);
cell?.StyleIndex = null;
}
}
public void Clear()
{
ClearContents();
ClearFormats();
}
public IRange CopyTo(uint rowIndex, uint colIndex, out IRange copiedRange)
{
_writer.ThrowIfDisposed();
lock (_writer._syncLock)
{
CopyData(_rowStart, _colStart, _rowEnd, _colEnd, rowIndex, colIndex);
copiedRange = new ExcelRange(_writer, _sheet, rowIndex, colIndex, rowIndex + Rows - 1, colIndex + Cols - 1);
return this;
}
}
public IRange CopyTo(uint rowIndex, string colIndex, out IRange copiedRange)
{
uint col = CellAddressHelper.ColumnLetterToIndex(colIndex);
return CopyTo(rowIndex, col, out copiedRange);
}
// Вспомогательные методы
private Cell? GetCellInternal(uint row, uint col)
{
var sheetData = _sheet.GetSheetData();
var rowElement = FindRowElement(sheetData, row);
if (rowElement == null) return null;
return FindCellInRow(rowElement, col);
}
private void CopyData(uint srcRowStart, uint srcColStart, uint srcRowEnd, uint srcColEnd, uint dstRowStart, uint dstColStart)
{
// Сохраняем все значения и форматы из исходного диапазона
var cellsData = new List<(uint row, uint col, Cell cell)>();
for (uint r = srcRowStart; r <= srcRowEnd; r++)
{
for (uint c = srcColStart; c <= srcColEnd; c++)
{
var cell = GetCellInternal(r, c);
if (cell != null)
{
var cloned = (Cell)cell.CloneNode(true);
cellsData.Add((r - srcRowStart + dstRowStart, c - srcColStart + dstColStart, cloned));
}
}
}
// Вставляем в новый диапазон
foreach (var (row, col, clonedCell) in cellsData)
{
InsertCellAt(row, col, clonedCell);
}
// Копируем ширины столбцов
for (uint c = srcColStart; c <= srcColEnd; c++)
{
var srcCol = GetColumnElementForIndex(c);
if (srcCol?.Width is { } width)
{
uint targetCol = c - srcColStart + dstColStart;
var dstCol = GetOrCreateColumnElementForIndex(targetCol);
dstCol.Width = width;
dstCol.CustomWidth = srcCol.CustomWidth;
}
}
// Копируем высоты строк
for (uint r = srcRowStart; r <= srcRowEnd; r++)
{
var srcRow = FindRowElement(_sheet.GetSheetData(), r);
if (srcRow is { } row
&& row.Height?.Value is { } hei
&& row.CustomHeight?.Value == true)
{
uint targetRow = r - srcRowStart + dstRowStart;
var dstRow = GetOrCreateRowElement(targetRow);
dstRow.Height = hei;
dstRow.CustomHeight = true;
}
}
}
private Row GetOrCreateRowElement(uint rowIndex)
{
var existing = FindRowElement(_sheet.GetSheetData(), rowIndex);
if (existing != null) return existing;
var newRow = new Row { RowIndex = rowIndex };
InsertRowElement(_sheet.GetSheetData(), newRow, rowIndex);
return newRow;
}
private static Row? FindRowElement(SheetData sheetData, uint rowIndex)
{
foreach (var row in sheetData.Elements<Row>())
if (row.RowIndex?.Value == rowIndex) return row;
return null;
}
private static void InsertRowElement(SheetData sheetData, Row row, uint rowIndex)
{
bool inserted = false;
foreach (var existing in sheetData.Elements<Row>().ToList())
{
if (existing.RowIndex?.Value > rowIndex)
{
existing.InsertBeforeSelf(row);
inserted = true;
break;
}
}
if (!inserted) sheetData.Append(row);
}
private Cell? FindCellInRow(Row row, uint colIndex)
{
foreach (var cell in row.Elements<Cell>())
{
if (CellAddressHelper.TryParseCellReference(cell.CellReference?.Value ?? string.Empty, out _, out uint col) && col == colIndex)
return cell;
}
return null;
}
private Column? GetColumnElementForIndex(uint col)
{
var worksheet = _sheet.Worksheet;
var cols = worksheet.GetFirstChild<Columns>();
if (cols == null) return null;
foreach (Column c in cols.Elements<Column>())
if (c.Min?.Value is { } min
&& c.Max?.Value is { } max
&& min <= col
&& max >= col)
return c;
return null;
}
private Column GetOrCreateColumnElementForIndex(uint col)
{
var existing = GetColumnElementForIndex(col);
if (existing != null) return existing;
var worksheet = _sheet.Worksheet;
var cols = worksheet.GetFirstChild<Columns>();
if (cols == null)
{
cols = new Columns();
worksheet.InsertAt(cols, 0);
}
var newCol = new Column
{
Min = col,
Max = col,
Width = 8.43,
CustomWidth = true
};
cols.Append(newCol);
return newCol;
}
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;
}
// создаёт стиль только с числовым форматом
private int GetOrCreateNumberFormatId(NumberFormatPattern format)
{
return _writer.GetOrCreateCellFormatId(numberFormat: format);
}
}