2026-06-08 14:31:31 +07:00
|
|
|
|
namespace QWERTYkez.ExcelProcessor;
|
2026-06-05 15:58:03 +07:00
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Внутренняя реализация <see cref="ISheet"/>.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
internal sealed class ExcelSheet : ISheet
|
|
|
|
|
|
{
|
|
|
|
|
|
internal readonly ExcelWriter Book;
|
|
|
|
|
|
internal readonly Sheet SheetElement;
|
|
|
|
|
|
internal readonly Worksheet Worksheet;
|
|
|
|
|
|
internal readonly uint SheetId;
|
|
|
|
|
|
|
|
|
|
|
|
internal ExcelSheet(ExcelWriter book, Sheet sheetElement, uint sheetId)
|
|
|
|
|
|
{
|
|
|
|
|
|
Book = book;
|
|
|
|
|
|
SheetElement = sheetElement;
|
|
|
|
|
|
SheetId = sheetId;
|
|
|
|
|
|
|
|
|
|
|
|
string partId = SheetElement.Id?.Value ?? string.Empty;
|
|
|
|
|
|
if (string.IsNullOrEmpty(partId))
|
|
|
|
|
|
throw new InvalidOperationException("Sheet has no relationship ID");
|
|
|
|
|
|
if (Book._doc.WorkbookPart?.GetPartById(partId) is not WorksheetPart part)
|
|
|
|
|
|
throw new InvalidOperationException("WorksheetPart not found");
|
|
|
|
|
|
if (part.Worksheet is not Worksheet worksheet)
|
|
|
|
|
|
throw new InvalidOperationException("WorksheetPart not found");
|
|
|
|
|
|
Worksheet = worksheet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public int Index => (int)SheetId;
|
|
|
|
|
|
public string Name => SheetElement.Name?.Value ?? string.Empty;
|
|
|
|
|
|
|
|
|
|
|
|
public bool TrySetName(string name)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (string.IsNullOrEmpty(name)) return false;
|
|
|
|
|
|
Book.ThrowIfDisposed();
|
|
|
|
|
|
SheetElement.Name = name;
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public IRow Row(uint row) => new ExcelRow(Book, this, row);
|
|
|
|
|
|
public ISheet Row(uint row, Action<IRow> edit)
|
|
|
|
|
|
{
|
|
|
|
|
|
edit(Row(row));
|
|
|
|
|
|
return this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public IColumn Col(uint col) => new ExcelColumn(Book, this, col);
|
|
|
|
|
|
public ISheet Col(uint col, Action<IColumn> edit)
|
|
|
|
|
|
{
|
|
|
|
|
|
edit(Col(col));
|
|
|
|
|
|
return this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public IColumn Col(string col) => Col(CellAddressHelper.ColumnLetterToIndex(col));
|
|
|
|
|
|
public ISheet Col(string col, Action<IColumn> edit)
|
|
|
|
|
|
{
|
|
|
|
|
|
edit(Col(col));
|
|
|
|
|
|
return this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public ICell Cell(uint row, uint col) => new ExcelCell(Book, this, row, col);
|
|
|
|
|
|
public ISheet Cell(uint row, uint col, Action<ICell> edit)
|
|
|
|
|
|
{
|
|
|
|
|
|
edit(Cell(row, col));
|
|
|
|
|
|
return this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public ISheet Cell(uint row, uint col, string value)
|
|
|
|
|
|
{
|
|
|
|
|
|
Cell(row, col).Set(value); return this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public ISheet Cell(uint row, uint col, bool value)
|
|
|
|
|
|
{
|
|
|
|
|
|
Cell(row, col).Set(value); return this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public ISheet Cell(uint row, uint col, string formula, NumberFormatPattern? format = null)
|
|
|
|
|
|
{
|
2026-06-19 15:06:40 +07:00
|
|
|
|
Cell(row, col).SetFormula(formula, format); return this;
|
2026-06-05 15:58:03 +07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public ISheet Cell(uint row, uint col, DateTime value, NumberFormatPattern? format = null)
|
|
|
|
|
|
{
|
|
|
|
|
|
Cell(row, col).Set(value, format); return this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public ISheet Cell(uint row, uint col, decimal value, NumberFormatPattern? format = null)
|
|
|
|
|
|
{
|
|
|
|
|
|
Cell(row, col).Set(value, format); return this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public ISheet Cell(uint row, uint col, double value, NumberFormatPattern? format = null)
|
|
|
|
|
|
{
|
|
|
|
|
|
Cell(row, col).Set(value, format); return this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public ISheet Cell(uint row, uint col, float value, NumberFormatPattern? format = null)
|
|
|
|
|
|
{
|
|
|
|
|
|
Cell(row, col).Set(value, format); return this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public ISheet Cell(uint row, uint col, int value, NumberFormatPattern? format = null)
|
|
|
|
|
|
{
|
|
|
|
|
|
Cell(row, col).Set(value, format); return this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public ISheet Cell(uint row, uint col, long value, NumberFormatPattern? format = null)
|
|
|
|
|
|
{
|
|
|
|
|
|
Cell(row, col).Set(value, format); return this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public ICell Cell(uint row, string col) => Cell(row, CellAddressHelper.ColumnLetterToIndex(col));
|
|
|
|
|
|
public ISheet Cell(uint row, string col, Action<ICell> edit)
|
|
|
|
|
|
{
|
|
|
|
|
|
edit(Cell(row, col));
|
|
|
|
|
|
return this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public ISheet Cell(uint row, string col, string value)
|
|
|
|
|
|
{
|
|
|
|
|
|
Cell(row, col).Set(value); return this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public ISheet Cell(uint row, string col, bool value)
|
|
|
|
|
|
{
|
|
|
|
|
|
Cell(row, col).Set(value); return this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public ISheet Cell(uint row, string col, string formula, NumberFormatPattern? format = null)
|
|
|
|
|
|
{
|
2026-06-19 15:06:40 +07:00
|
|
|
|
Cell(row, col).SetFormula(formula, format); return this;
|
2026-06-05 15:58:03 +07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public ISheet Cell(uint row, string col, DateTime value, NumberFormatPattern? format = null)
|
|
|
|
|
|
{
|
|
|
|
|
|
Cell(row, col).Set(value, format); return this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public ISheet Cell(uint row, string col, decimal value, NumberFormatPattern? format = null)
|
|
|
|
|
|
{
|
|
|
|
|
|
Cell(row, col).Set(value, format); return this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public ISheet Cell(uint row, string col, double value, NumberFormatPattern? format = null)
|
|
|
|
|
|
{
|
|
|
|
|
|
Cell(row, col).Set(value, format); return this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public ISheet Cell(uint row, string col, float value, NumberFormatPattern? format = null)
|
|
|
|
|
|
{
|
|
|
|
|
|
Cell(row, col).Set(value, format); return this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public ISheet Cell(uint row, string col, int value, NumberFormatPattern? format = null)
|
|
|
|
|
|
{
|
|
|
|
|
|
Cell(row, col).Set(value, format); return this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public ISheet Cell(uint row, string col, long value, NumberFormatPattern? format = null)
|
|
|
|
|
|
{
|
|
|
|
|
|
Cell(row, col).Set(value, format); return this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void ClearContents()
|
|
|
|
|
|
{
|
|
|
|
|
|
var sheetData = GetSheetData();
|
|
|
|
|
|
foreach (var row in sheetData.Elements<Row>().ToList())
|
|
|
|
|
|
{
|
|
|
|
|
|
foreach (var cell in row.Elements<Cell>().ToList())
|
|
|
|
|
|
cell.Remove();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void ClearFormats()
|
|
|
|
|
|
{
|
|
|
|
|
|
var sheetData = GetSheetData();
|
|
|
|
|
|
foreach (var row in sheetData.Elements<Row>())
|
|
|
|
|
|
{
|
|
|
|
|
|
foreach (var cell in row.Elements<Cell>())
|
|
|
|
|
|
cell.StyleIndex = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void Clear()
|
|
|
|
|
|
{
|
|
|
|
|
|
ClearContents();
|
|
|
|
|
|
ClearFormats();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void Remove() => Book.TryRemoveSheet(this);
|
|
|
|
|
|
|
|
|
|
|
|
// Вспомогательные методы
|
|
|
|
|
|
|
|
|
|
|
|
internal SheetData GetSheetData()
|
|
|
|
|
|
{
|
|
|
|
|
|
var worksheet = Worksheet;
|
|
|
|
|
|
var sheetData = worksheet.GetFirstChild<SheetData>();
|
|
|
|
|
|
if (sheetData == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
sheetData = new SheetData();
|
|
|
|
|
|
worksheet.Append(sheetData);
|
|
|
|
|
|
}
|
|
|
|
|
|
return sheetData;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#region Range Operations
|
|
|
|
|
|
|
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
|
|
public IRange RangeByIndexes(uint startRow, uint startCol, uint endRow, uint endCol)
|
|
|
|
|
|
{
|
2026-06-17 10:39:00 +07:00
|
|
|
|
if (startRow == 0) throw new ArgumentException("startRow must be >= 1", nameof(startRow));
|
|
|
|
|
|
if (startCol == 0) throw new ArgumentException("startCol must be >= 1", nameof(startCol));
|
|
|
|
|
|
if (endRow == 0) throw new ArgumentException("endRow must be >= 1", nameof(endRow));
|
|
|
|
|
|
if (endCol == 0) throw new ArgumentException("endCol must be >= 1", nameof(endCol));
|
|
|
|
|
|
|
|
|
|
|
|
// Приводим к корректному порядку (пользователь мог передать start > end)
|
|
|
|
|
|
uint rowStart = Math.Min(startRow, endRow);
|
|
|
|
|
|
uint rowEnd = Math.Max(startRow, endRow);
|
|
|
|
|
|
uint colStart = Math.Min(startCol, endCol);
|
|
|
|
|
|
uint colEnd = Math.Max(startCol, endCol);
|
|
|
|
|
|
|
|
|
|
|
|
return new ExcelRange(Book, this, rowStart, colStart, rowEnd, colEnd);
|
2026-06-05 15:58:03 +07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
|
|
public IRange RangeByIndexes(uint startRow, string startCol, uint endRow, string endCol)
|
|
|
|
|
|
{
|
2026-06-17 10:39:00 +07:00
|
|
|
|
if (string.IsNullOrEmpty(startCol)) throw new ArgumentException("startCol cannot be null or empty", nameof(startCol));
|
|
|
|
|
|
if (string.IsNullOrEmpty(endCol)) throw new ArgumentException("endCol cannot be null or empty", nameof(endCol));
|
|
|
|
|
|
|
2026-06-05 15:58:03 +07:00
|
|
|
|
uint startColIdx = CellAddressHelper.ColumnLetterToIndex(startCol);
|
|
|
|
|
|
uint endColIdx = CellAddressHelper.ColumnLetterToIndex(endCol);
|
2026-06-17 10:39:00 +07:00
|
|
|
|
if (startColIdx == 0) throw new ArgumentException($"Invalid column letter: '{startCol}'", nameof(startCol));
|
|
|
|
|
|
if (endColIdx == 0) throw new ArgumentException($"Invalid column letter: '{endCol}'", nameof(endCol));
|
|
|
|
|
|
|
|
|
|
|
|
return RangeByIndexes(startRow, startColIdx, endRow, endColIdx);
|
2026-06-05 15:58:03 +07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <inheritdoc />
|
2026-06-17 10:39:00 +07:00
|
|
|
|
public IRange RangeByLength(uint startRow, uint startCol, uint rows, uint cols)
|
2026-06-05 15:58:03 +07:00
|
|
|
|
{
|
2026-06-17 10:39:00 +07:00
|
|
|
|
if (startRow == 0) throw new ArgumentException("startRow must be >= 1", nameof(startRow));
|
|
|
|
|
|
if (startCol == 0) throw new ArgumentException("startCol must be >= 1", nameof(startCol));
|
|
|
|
|
|
if (rows == 0) throw new ArgumentException("rows must be > 0", nameof(rows));
|
|
|
|
|
|
if (cols == 0) throw new ArgumentException("cols must be > 0", nameof(cols));
|
|
|
|
|
|
|
|
|
|
|
|
checked
|
|
|
|
|
|
{
|
|
|
|
|
|
uint endRow = startRow + rows - 1;
|
|
|
|
|
|
uint endCol = startCol + cols - 1;
|
|
|
|
|
|
return new ExcelRange(Book, this, startRow, startCol, endRow, endCol);
|
|
|
|
|
|
}
|
2026-06-05 15:58:03 +07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <inheritdoc />
|
2026-06-17 10:39:00 +07:00
|
|
|
|
public IRange RangeByLength(uint startRow, string startCol, uint rows, uint cols)
|
2026-06-05 15:58:03 +07:00
|
|
|
|
{
|
2026-06-17 10:39:00 +07:00
|
|
|
|
if (string.IsNullOrEmpty(startCol)) throw new ArgumentException("startCol cannot be null or empty", nameof(startCol));
|
|
|
|
|
|
uint startColIdx = CellAddressHelper.ColumnLetterToIndex(startCol);
|
|
|
|
|
|
if (startColIdx == 0) throw new ArgumentException($"Invalid column letter: '{startCol}'", nameof(startCol));
|
|
|
|
|
|
return RangeByLength(startRow, startColIdx, rows, cols);
|
2026-06-05 15:58:03 +07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <inheritdoc />
|
2026-06-17 10:39:00 +07:00
|
|
|
|
public ISheet RangeByIndexes(uint startRow, uint startCol, uint endRow, uint endCol, Action<IRange> edit)
|
2026-06-05 15:58:03 +07:00
|
|
|
|
{
|
2026-06-17 10:39:00 +07:00
|
|
|
|
if (edit is null) throw new ArgumentNullException(nameof(edit));
|
|
|
|
|
|
var range = RangeByIndexes(startRow, startCol, endRow, endCol);
|
|
|
|
|
|
edit(range);
|
|
|
|
|
|
return this;
|
2026-06-05 15:58:03 +07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <inheritdoc />
|
2026-06-17 10:39:00 +07:00
|
|
|
|
public ISheet RangeByIndexes(uint startRow, string startCol, uint endRow, string endCol, Action<IRange> edit)
|
2026-06-05 15:58:03 +07:00
|
|
|
|
{
|
2026-06-17 10:39:00 +07:00
|
|
|
|
if (edit is null) throw new ArgumentNullException(nameof(edit));
|
|
|
|
|
|
var range = RangeByIndexes(startRow, startCol, endRow, endCol);
|
|
|
|
|
|
edit(range);
|
|
|
|
|
|
return this;
|
2026-06-05 15:58:03 +07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
|
|
public ISheet RangeByLength(uint startRow, uint startCol, uint rows, uint cols, Action<IRange> edit)
|
|
|
|
|
|
{
|
2026-06-17 10:39:00 +07:00
|
|
|
|
if (edit is null) throw new ArgumentNullException(nameof(edit));
|
2026-06-05 15:58:03 +07:00
|
|
|
|
var range = RangeByLength(startRow, startCol, rows, cols);
|
|
|
|
|
|
edit(range);
|
|
|
|
|
|
return this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
|
|
public ISheet RangeByLength(uint startRow, string startCol, uint rows, uint cols, Action<IRange> edit)
|
|
|
|
|
|
{
|
2026-06-17 10:39:00 +07:00
|
|
|
|
if (edit is null) throw new ArgumentNullException(nameof(edit));
|
2026-06-05 15:58:03 +07:00
|
|
|
|
var range = RangeByLength(startRow, startCol, rows, cols);
|
|
|
|
|
|
edit(range);
|
|
|
|
|
|
return this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
2026-06-17 10:39:00 +07:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#region Merge Operations
|
|
|
|
|
|
|
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
|
|
public bool TryMergeByIndexes(uint startRow, uint startCol, uint endRow, uint endCol) =>
|
|
|
|
|
|
RangeByIndexes(startRow, startCol, endRow, endCol).TryMerge();
|
|
|
|
|
|
|
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
|
|
public bool TryMergeByIndexes(uint startRow, string startCol, uint endRow, string endCol) =>
|
|
|
|
|
|
RangeByIndexes(startRow, startCol, endRow, endCol).TryMerge();
|
|
|
|
|
|
|
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
|
|
public bool TryMergeByLength(uint startRow, uint startCol, uint rows, uint cols) =>
|
|
|
|
|
|
RangeByLength(startRow, startCol, rows, cols).TryMerge();
|
|
|
|
|
|
|
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
|
|
public bool TryMergeByLength(uint startRow, string startCol, uint rows, uint cols) =>
|
|
|
|
|
|
RangeByLength(startRow, startCol, rows, cols).TryMerge();
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
2026-06-05 15:58:03 +07:00
|
|
|
|
}
|