namespace QWERTYkez.ExcelProcessor; /// /// Внутренняя реализация . /// 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; } /// public uint Index => _rowIndex; /// Устанавливает высоту строки без создания объекта IRow. 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; } } /// Возвращает высоту строки. 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()) 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().ToList()) { if (existing.RowIndex?.Value > rowIndex) { existing.InsertBeforeSelf(row); inserted = true; break; } } if (!inserted) sheetData.Append(row); } /// 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; } /// 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; } } } /// 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.StyleIndex = (uint)formatIndex; } return this; } /// public ICell Cell(uint col) => new ExcelCell(_writer, _sheet, _rowIndex, col); /// public IRow Cell(uint col, Action 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 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; } /// public ICell Cell(string col) => Cell(CellAddressHelper.ColumnLetterToIndex(col)); /// public IRow EditCell(string col, Action edit) { edit(Cell(col)); return this; } /// public void ClearContents() { _writer.ThrowIfDisposed(); lock (_writer._syncLock) { var rowElement = FindRowElement(_sheet.GetSheetData(), _rowIndex); if (rowElement == null) return; foreach (var cell in rowElement.Elements().ToList()) { cell.Remove(); } } } /// 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.StyleIndex = null; } } } /// public void Clear() { ClearContents(); ClearFormats(); } /// public void Remove() { _writer.ThrowIfDisposed(); lock (_writer._syncLock) { var sheetData = _sheet.GetSheetData(); var rowElement = FindRowElement(sheetData, _rowIndex); rowElement?.Remove(); // Сдвиг индексов последующих строк вниз в Open XML не требуется, т.к. они имеют свои RowIndex. // Но при последующем доступе по индексам нужно будет корректировать. } } /// 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()) { 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().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); } }