Files
QWERTYkez.OpenXmlProcessors/QWERTYkez.ExcelProcessor/Editors/ExcelRow.cs

393 lines
12 KiB
C#
Raw Normal View History

2026-06-08 14:31:31 +07:00
namespace QWERTYkez.ExcelProcessor;
/// <summary>
/// Внутренняя реализация <see cref="IRow"/>.
/// </summary>
internal sealed class ExcelRow : IRow
{
private readonly ExcelWriter _writer;
private readonly ExcelSheet _sheet;
private 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); // стандартная высота
}
}
// Вспомогательные методы
private 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;
}
private 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;
}
private 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;
}
}
}
/// <inheritdoc />
public IRow SetNumberFormat(NumberFormatPattern format)
{
if (format == null) return this;
_writer.ThrowIfDisposed();
lock (_writer._syncLock)
{
// Находим все ячейки в этой строке и устанавливаем формат
var sheetData = _sheet.GetSheetData();
var rowElement = FindRowElement(sheetData, _rowIndex);
if (rowElement == null) return this;
int formatIndex = GetOrCreateNumberFormatId(format);
foreach (var cell in rowElement.Elements<Cell>())
cell.StyleIndex = (uint)formatIndex;
}
return this;
}
/// <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).Set(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).Set(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;
}
}
// Вспомогательные методы
private 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;
}
private 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;
}
private 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);
}
private int GetOrCreateNumberFormatId(NumberFormatPattern format)
{
// Создаём стиль, содержащий только числовой формат, и возвращаем его индекс
return _writer.GetOrCreateCellFormatId(numberFormat: format);
}
}