Files
QWERTYkez.OpenXmlProcessors/QWERTYkez.ExcelProcessor/Editors/ExcelRow.cs
melekhin f5eb667973 0.9.1
2026-06-08 14:31:31 +07:00

393 lines
12 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;
/// <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);
}
}