Files
melekhin e373d4108a
All checks were successful
Publish NuGet packages / publish (push) Successful in 28s
many debugs
2026-06-19 15:06:40 +07:00

525 lines
16 KiB
C#
Raw Permalink 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="IRow"/>.
/// </summary>
internal sealed class ExcelRow : IRow
{
readonly ExcelWriter _writer;
readonly ExcelSheet _sheet;
uint _rowIndex;
internal ExcelRow(ExcelWriter writer, ExcelSheet sheet, uint rowIndex)
{
_writer = writer;
_sheet = sheet;
_rowIndex = rowIndex;
}
/// <inheritdoc />
public uint Index => _rowIndex;
/// <summary>Устанавливает высоту строки без создания объекта IRow.</summary>
internal static void SetHeight(ExcelWriter writer, ExcelSheet sheet, uint rowIndex, RowHeight height)
{
writer.ThrowIfDisposed();
lock (writer._syncLock)
{
var row = GetOrCreateRowElementInternal(sheet, rowIndex);
row.Height = height.Points;
row.CustomHeight = true;
}
}
/// <summary>Возвращает высоту строки.</summary>
internal static RowHeight GetHeight(ExcelWriter writer, ExcelSheet sheet, uint rowIndex)
{
writer.ThrowIfDisposed();
lock (writer._syncLock)
{
var row = GetRowElementInternal(sheet, rowIndex);
if (row is { } r && r.CustomHeight?.HasValue == true && r.Height?.Value is { } hei)
return RowHeight.FromPoints(hei);
return RowHeight.FromPoints(15.0); // стандартная высота
}
}
// Вспомогательные методы
static Row? GetRowElementInternal(ExcelSheet sheet, uint rowIndex)
{
var sheetData = sheet.GetSheetData();
foreach (var row in sheetData.Elements<Row>())
if (row.RowIndex?.Value == rowIndex) return row;
return null;
}
static Row GetOrCreateRowElementInternal(ExcelSheet sheet, uint rowIndex)
{
var existing = GetRowElementInternal(sheet, rowIndex);
if (existing != null) return existing;
var sheetData = sheet.GetSheetData();
var newRow = new Row { RowIndex = rowIndex };
InsertRowElementInternal(sheetData, newRow, rowIndex);
return newRow;
}
static void InsertRowElementInternal(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);
}
/// <inheritdoc />
public IRow MoveTo(uint index)
{
if (index == _rowIndex) return this;
_writer.ThrowIfDisposed();
lock (_writer._syncLock)
{
var sheetData = _sheet.GetSheetData();
// Находим элемент строки
var rowElement = FindRowElement(sheetData, _rowIndex);
if (rowElement == null)
{
// Пустая строка — просто меняем индекс
_rowIndex = index;
return this;
}
// Удаляем из текущей позиции
rowElement.Remove();
// Вставляем в новую позицию
InsertRowElement(sheetData, rowElement, index);
// Обновляем RowIndex в элементе
rowElement.RowIndex = index;
_rowIndex = index;
}
return this;
}
/// <inheritdoc />
public RowHeight Height
{
get
{
_writer.ThrowIfDisposed();
lock (_writer._syncLock)
{
var rowElement = GetOrCreateRowElement();
if (rowElement.Height?.Value is double h && rowElement.CustomHeight?.Value == true)
return RowHeight.FromPoints(h);
// Стандартная высота, если не задана (обычно 15 пунктов)
return RowHeight.FromPoints(15.0);
}
}
set
{
_writer.ThrowIfDisposed();
lock (_writer._syncLock)
{
var rowElement = GetOrCreateRowElement();
rowElement.Height = value.Points;
rowElement.CustomHeight = true;
}
}
}
private void ApplyStyleToRowCells(CellStyle style)
{
var sheetData = _sheet.GetSheetData();
var rowElement = FindRowElement(sheetData, _rowIndex);
if (rowElement == null) return;
// Применяем стиль ко всем существующим ячейкам
foreach (var cellElement in rowElement.Elements<Cell>())
{
var colIndex = GetColumnIndex(cellElement.CellReference?.Value);
if (colIndex > 0)
{
var cell = new ExcelCell(_writer, _sheet, _rowIndex, colIndex);
// Применяем стиль через Set, чтобы объединить с существующим
cell.Set(style);
}
}
}
static uint GetColumnIndex(string? cellReference)
{
if (string.IsNullOrEmpty(cellReference)) return 0;
int i = 0;
while (i < cellReference!.Length && char.IsLetter(cellReference[i])) i++;
if (i == 0) return 0;
string colPart = cellReference.Substring(0, i);
return CellAddressHelper.ColumnLetterToIndex(colPart);
}
CellStyle? GetRowStyle(Row rowElement)
{
if (rowElement.StyleIndex?.Value is not uint styleIndex)
return null;
return _writer.GetCellStyle(styleIndex);
}
/// <inheritdoc />
public IRow Set(NumberFormatPattern format)
{
if (format == null) return this;
_writer.ThrowIfDisposed();
lock (_writer._syncLock)
{
var rowElement = GetOrCreateRowElement();
var currentStyle = GetRowStyle(rowElement) ?? new CellStyle();
if (currentStyle.TryMerge(format, out var newStyle))
{
ApplyStyleToRow(newStyle);
ApplyStyleToRowCells(newStyle);
}
return this;
}
}
/// <inheritdoc />
public IRow Set(CellAlign align)
{
_writer.ThrowIfDisposed();
lock (_writer._syncLock)
{
var rowElement = GetOrCreateRowElement();
var currentStyle = GetRowStyle(rowElement) ?? new CellStyle();
if (currentStyle.TryMerge(align, out var newStyle))
{
ApplyStyleToRow(newStyle);
ApplyStyleToRowCells(newStyle);
}
return this;
}
}
/// <inheritdoc />
public IRow Set(CellBorder border)
{
_writer.ThrowIfDisposed();
lock (_writer._syncLock)
{
var rowElement = GetOrCreateRowElement();
var currentStyle = GetRowStyle(rowElement) ?? new CellStyle();
if (currentStyle.TryMerge(border, out var newStyle))
{
ApplyStyleToRow(newStyle);
ApplyStyleToRowCells(newStyle);
}
return this;
}
}
/// <inheritdoc />
public IRow Set(CellFill fill)
{
_writer.ThrowIfDisposed();
lock (_writer._syncLock)
{
var rowElement = GetOrCreateRowElement();
var currentStyle = GetRowStyle(rowElement) ?? new CellStyle();
if (currentStyle.TryMerge(fill, out var newStyle))
{
ApplyStyleToRow(newStyle);
ApplyStyleToRowCells(newStyle);
}
return this;
}
}
/// <inheritdoc />
public IRow Set(CellFont font)
{
_writer.ThrowIfDisposed();
lock (_writer._syncLock)
{
var rowElement = GetOrCreateRowElement();
var currentStyle = GetRowStyle(rowElement) ?? new CellStyle();
if (currentStyle.TryMerge(font, out var newStyle))
{
ApplyStyleToRow(newStyle);
ApplyStyleToRowCells(newStyle);
}
return this;
}
}
/// <inheritdoc />
public IRow Set(CellStyle style)
{
if (style == null) return this;
_writer.ThrowIfDisposed();
lock (_writer._syncLock)
{
var rowElement = GetOrCreateRowElement();
var currentStyle = GetRowStyle(rowElement) ?? new CellStyle();
if (currentStyle.TryMerge(style, out var newStyle))
{
ApplyStyleToRow(newStyle);
ApplyStyleToRowCells(newStyle);
}
return this;
}
}
void ApplyStyleToRow(CellStyle style)
{
var rowElement = GetOrCreateRowElement();
int styleIndex = _writer.GetOrCreateStyleId(style);
rowElement.StyleIndex = (uint)styleIndex;
rowElement.CustomFormat = true;
}
/// <inheritdoc />
public ICell Cell(uint col) => new ExcelCell(_writer, _sheet, _rowIndex, col);
/// <inheritdoc />
public IRow Cell(uint col, Action<ICell> edit)
{
edit(Cell(col)); return this;
}
public IRow Cell(uint col, string value)
{
Cell(col).Set(value); return this;
}
public IRow Cell(uint col, bool value)
{
Cell(col).Set(value); return this;
}
public IRow Cell(uint col, string value, NumberFormatPattern? format = null)
{
Cell(col).SetFormula(value, format); return this;
}
public IRow Cell(uint col, DateTime value, NumberFormatPattern? format = null)
{
Cell(col).Set(value, format); return this;
}
public IRow Cell(uint col, decimal value, NumberFormatPattern? format = null)
{
Cell(col).Set(value, format); return this;
}
public IRow Cell(uint col, double value, NumberFormatPattern? format = null)
{
Cell(col).Set(value, format); return this;
}
public IRow Cell(uint col, float value, NumberFormatPattern? format = null)
{
Cell(col).Set(value, format); return this;
}
public IRow Cell(uint col, int value, NumberFormatPattern? format = null)
{
Cell(col).Set(value, format); return this;
}
public IRow Cell(uint col, long value, NumberFormatPattern? format = null)
{
Cell(col).Set(value, format); return this;
}
public IRow Cell(string col, Action<ICell> edit)
{
edit(Cell(col)); return this;
}
public IRow Cell(string col, string value)
{
Cell(col).Set(value); return this;
}
public IRow Cell(string col, bool value)
{
Cell(col).Set(value); return this;
}
public IRow Cell(string col, string value, NumberFormatPattern? format = null)
{
Cell(col).SetFormula(value, format); return this;
}
public IRow Cell(string col, DateTime value, NumberFormatPattern? format = null)
{
throw new NotImplementedException();
}
public IRow Cell(string col, decimal value, NumberFormatPattern? format = null)
{
Cell(col).Set(value, format); return this;
}
public IRow Cell(string col, double value, NumberFormatPattern? format = null)
{
Cell(col).Set(value, format); return this;
}
public IRow Cell(string col, float value, NumberFormatPattern? format = null)
{
Cell(col).Set(value, format); return this;
}
public IRow Cell(string col, int value, NumberFormatPattern? format = null)
{
Cell(col).Set(value, format); return this;
}
public IRow Cell(string col, long value, NumberFormatPattern? format = null)
{
Cell(col).Set(value, format); return this;
}
/// <inheritdoc />
public ICell Cell(string col) => Cell(CellAddressHelper.ColumnLetterToIndex(col));
/// <inheritdoc />
public IRow EditCell(string col, Action<ICell> edit)
{
edit(Cell(col)); return this;
}
/// <inheritdoc />
public void ClearContents()
{
_writer.ThrowIfDisposed();
lock (_writer._syncLock)
{
var rowElement = FindRowElement(_sheet.GetSheetData(), _rowIndex);
if (rowElement == null) return;
foreach (var cell in rowElement.Elements<Cell>().ToList())
{
cell.Remove();
}
}
}
/// <inheritdoc />
public void ClearFormats()
{
_writer.ThrowIfDisposed();
lock (_writer._syncLock)
{
var rowElement = FindRowElement(_sheet.GetSheetData(), _rowIndex);
if (rowElement == null) return;
foreach (var cell in rowElement.Elements<Cell>())
{
cell.StyleIndex = null;
}
}
}
/// <inheritdoc />
public void Clear()
{
ClearContents();
ClearFormats();
}
/// <inheritdoc />
public void Remove()
{
_writer.ThrowIfDisposed();
lock (_writer._syncLock)
{
var sheetData = _sheet.GetSheetData();
var rowElement = FindRowElement(sheetData, _rowIndex);
rowElement?.Remove();
// Сдвиг индексов последующих строк вниз в Open XML не требуется, т.к. они имеют свои RowIndex.
// Но при последующем доступе по индексам нужно будет корректировать.
}
}
/// <inheritdoc />
public IRow CopyTo(uint index, out IRow copiedRow)
{
_writer.ThrowIfDisposed();
lock (_writer._syncLock)
{
var sourceRow = FindRowElement(_sheet.GetSheetData(), _rowIndex);
if (sourceRow == null)
{
copiedRow = new ExcelRow(_writer, _sheet, index);
return this;
}
// Клонируем элемент строки
var clonedRow = (Row)sourceRow.CloneNode(true);
clonedRow.RowIndex = index;
// Вставляем в новую позицию
var sheetData = _sheet.GetSheetData();
InsertRowElement(sheetData, clonedRow, index);
copiedRow = new ExcelRow(_writer, _sheet, index);
return this;
}
}
// Вспомогательные методы
Row GetOrCreateRowElement()
{
var sheetData = _sheet.GetSheetData();
var existing = FindRowElement(sheetData, _rowIndex);
if (existing != null) return existing;
var newRow = new Row { RowIndex = _rowIndex };
InsertRowElement(sheetData, newRow, _rowIndex);
return newRow;
}
static Row? FindRowElement(SheetData sheetData, uint rowIndex)
{
// Поиск по атрибуту RowIndex. В Open XML строки могут идти не по порядку.
foreach (var row in sheetData.Elements<Row>())
{
if (row.RowIndex?.Value == rowIndex)
return row;
}
return null;
}
static void InsertRowElement(SheetData sheetData, Row row, uint rowIndex)
{
// Вставка с сохранением сортировки по 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);
}
int GetOrCreateNumberFormatId(NumberFormatPattern format)
{
// Создаём стиль, содержащий только числовой формат, и возвращаем его индекс
return _writer.GetOrCreateCellFormatId(numberFormat: format);
}
}