namespace QWERTYkez.ExcelProcessor; /// /// Внутренняя реализация . /// 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; } /// 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); // стандартная высота } } // Вспомогательные методы 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; } 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().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; } } } private void ApplyStyleToRowCells(CellStyle style) { var sheetData = _sheet.GetSheetData(); var rowElement = FindRowElement(sheetData, _rowIndex); if (rowElement == null) return; // Применяем стиль ко всем существующим ячейкам foreach (var cellElement in rowElement.Elements()) { 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); } /// 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; } } /// 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; } } /// 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; } } /// 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; } } /// 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; } } /// 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; } /// 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).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 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; } /// 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; } } // Вспомогательные методы 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()) { 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().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); } }