namespace QWERTYkez.ExcelProcessor; /// /// Внутренняя реализация . /// internal sealed class ExcelColumn : IColumn { internal readonly ExcelWriter _writer; internal readonly ExcelSheet _sheet; internal uint _colIndex; internal ExcelColumn(ExcelWriter writer, ExcelSheet sheet, uint colIndex) { _writer = writer; _sheet = sheet; _colIndex = colIndex; } public uint Index => _colIndex; public string IndexLetter => NumberToColumnLetter(_colIndex); /// Устанавливает ширину столбца без создания объекта IColumn. internal static void SetWidth(ExcelWriter writer, ExcelSheet sheet, uint colIndex, ColumnWidth width) { writer.ThrowIfDisposed(); lock (writer._syncLock) { var col = GetOrCreateColumnElementInternal(sheet, colIndex); double widthInChars; if (width.IsCharacterUnit) { // Если ширина задана в символах, используем напрямую widthInChars = width.CharacterValue; } else { double targetPoints = width.GetTargetPoints(); widthInChars = writer.TryGetCalibrateCoeff(targetPoints, out var closestCw) ? closestCw : targetPoints / ColumnWidth.DefaultPointsPerChar; } col.Width = widthInChars; col.CustomWidth = true; } } /// Возвращает ширину столбца. internal static ColumnWidth GetWidth(ExcelWriter writer, ExcelSheet sheet, uint colIndex) { writer.ThrowIfDisposed(); lock (writer._syncLock) { var col = GetColumnElementInternal(sheet, colIndex); if (col is Column column && column.CustomWidth?.Value == true && column.Width?.Value is { } wid) return ColumnWidth.FromCharacters(wid); return ColumnWidth.FromCharacters(8.43); // стандартная ширина } } // Вспомогательные внутренние методы (перенести существующую логику) private static Column? GetColumnElementInternal(ExcelSheet sheet, uint colIndex) { var cols = sheet.Worksheet.GetFirstChild(); if (cols == null) return null; foreach (Column col in cols.Elements()) { if (col?.Min?.Value is { } min && col?.Max?.Value is { } max && min <= colIndex && max >= colIndex) return col; } return null; } private static Column GetOrCreateColumnElementInternal(ExcelSheet sheet, uint colIndex) { var existing = GetColumnElementInternal(sheet, colIndex); if (existing != null) return existing; var worksheet = sheet.Worksheet; var cols = worksheet.GetFirstChild(); if (cols == null) { cols = new Columns(); worksheet.InsertAt(cols, 0); } var newCol = new Column { Min = colIndex, Max = colIndex, Width = 8.43, CustomWidth = true }; cols.Append(newCol); return newCol; } public IColumn MoveTo(uint index) { if (index == _colIndex) return this; _writer.ThrowIfDisposed(); lock (_writer._syncLock) { // Копируем данные в новую позицию CopyColumnData(_colIndex, index); // Очищаем исходный столбец ClearColumnData(_colIndex); _colIndex = index; } return this; } public IColumn MoveTo(string index) { uint idx = CellAddressHelper.ColumnLetterToIndex(index); return MoveTo(idx); } public ColumnWidth Width { get { _writer.ThrowIfDisposed(); lock (_writer._syncLock) { var column = GetColumnElement(); if (column is Column col && col.Width?.Value is { } val && col.CustomWidth?.Value == true) return ColumnWidth.FromCharacters(val); return ColumnWidth.FromCharacters(8.43); // стандартная ширина } } set { _writer.ThrowIfDisposed(); lock (_writer._syncLock) { var col = GetOrCreateColumnElement(); double widthInChars; if (value.IsCharacterUnit) { // Если ширина задана в символах, используем напрямую widthInChars = value.CharacterValue; } else { double targetCm = value.GetTargetCentimeters(); widthInChars = _writer.TryGetCalibrateCoeff(targetCm, out var closestCw) ? closestCw : targetCm * (ColumnWidth.DefaultPointsPerChar / 28.3464566929); } col.Width = widthInChars; col.CustomWidth = true; } } } public IColumn SetNumberFormat(NumberFormatPattern format) { if (format == null) return this; _writer.ThrowIfDisposed(); lock (_writer._syncLock) { int fmtId = GetOrCreateNumberFormatId(format); // Применяем ко всем ячейкам столбца var sheetData = _sheet.GetSheetData(); foreach (var row in sheetData.Elements()) { var cell = FindCellInRow(row, _colIndex); cell?.StyleIndex = (uint)fmtId; } } return this; } public ICell Cell(uint row) { return new ExcelCell(_writer, _sheet, row, _colIndex); } public IColumn Cell(uint row, Action edit) { var cell = Cell(row); edit(cell); return this; } public IColumn Cell(uint row, string value) { Cell(row).Set(value); return this; } public IColumn Cell(uint row, bool value) { Cell(row).Set(value); return this; } public IColumn Cell(uint row, string value, NumberFormatPattern? format = null) { Cell(row).Set(value, format); return this; } public IColumn Cell(uint row, DateTime value, NumberFormatPattern? format = null) { Cell(row).Set(value, format); return this; } public IColumn Cell(uint row, decimal value, NumberFormatPattern? format = null) { Cell(row).Set(value, format); return this; } public IColumn Cell(uint row, double value, NumberFormatPattern? format = null) { Cell(row).Set(value, format); return this; } public IColumn Cell(uint row, float value, NumberFormatPattern? format = null) { Cell(row).Set(value, format); return this; } public IColumn Cell(uint row, int value, NumberFormatPattern? format = null) { Cell(row).Set(value, format); return this; } public IColumn Cell(uint row, long value, NumberFormatPattern? format = null) { Cell(row).Set(value, format); return this; } public void ClearContents() { _writer.ThrowIfDisposed(); lock (_writer._syncLock) { ClearColumnData(_colIndex); } } public void ClearFormats() { _writer.ThrowIfDisposed(); lock (_writer._syncLock) { var sheetData = _sheet.GetSheetData(); foreach (var row in sheetData.Elements()) { var cell = FindCellInRow(row, _colIndex); cell?.StyleIndex = null; } } } public void Clear() { ClearContents(); ClearFormats(); } public void Remove() { _writer.ThrowIfDisposed(); lock (_writer._syncLock) { // Очищаем данные ClearColumnData(_colIndex); // Удаляем элемент Column, если он существует DeleteColumnElement(); } } public IColumn CopyTo(uint index, out IColumn copiedColumn) { _writer.ThrowIfDisposed(); lock (_writer._syncLock) { CopyColumnData(_colIndex, index); copiedColumn = new ExcelColumn(_writer, _sheet, index); return this; } } public IColumn CopyTo(string index, out IColumn copiedColumn) { uint idx = CellAddressHelper.ColumnLetterToIndex(index); return CopyTo(idx, out copiedColumn); } // Вспомогательные методы private Column? GetColumnElement() { var cols = _sheet.Worksheet.GetFirstChild(); if (cols == null) return null; foreach (Column col in cols.Elements()) { if (col?.Min?.Value is { } min && col?.Max?.Value is { } max && min <= _colIndex && max >= _colIndex) return col; } return null; } private Column GetOrCreateColumnElement() { var existing = GetColumnElement(); if (existing != null) return existing; var worksheet = _sheet.Worksheet; var cols = worksheet.GetFirstChild(); if (cols == null) { cols = new Columns(); worksheet.InsertAt(cols, 0); } var newCol = new Column { Min = _colIndex, Max = _colIndex, Width = 8.43, CustomWidth = true }; cols.Append(newCol); return newCol; } private void DeleteColumnElement() { var col = GetColumnElement(); col?.Remove(); } private void CopyColumnData(uint sourceCol, uint targetCol) { if (sourceCol == targetCol) return; var sheetData = _sheet.GetSheetData(); // Сначала удаляем существующие данные в целевом столбце (если нужно перезаписать) ClearColumnData(targetCol); // Копируем значения и форматы из sourceCol в targetCol foreach (var row in sheetData.Elements()) { var sourceCell = FindCellInRow(row, sourceCol); if (sourceCell != null) { var targetCell = FindCellInRow(row, targetCol); if (targetCell == null) { targetCell = new Cell(); // Вставляем в нужное место (по порядку столбцов) InsertCellInRow(row, targetCell, targetCol); } // Копируем содержимое и стиль targetCell.DataType = sourceCell.DataType; targetCell.CellValue = sourceCell.CellValue?.CloneNode(true) as CellValue; targetCell.CellFormula = sourceCell.CellFormula?.CloneNode(true) as CellFormula; targetCell.StyleIndex = sourceCell.StyleIndex; // Если есть InlineString, клонируем if (sourceCell.InlineString != null) targetCell.InlineString = (InlineString)sourceCell.InlineString.CloneNode(true); } } // Копируем ширину столбца var sourceColElem = GetColumnElementForIndex(sourceCol); if (sourceColElem?.Width is { } width) { var targetColElem = GetOrCreateColumnElementForIndex(targetCol); targetColElem.Width = width; targetColElem.CustomWidth = sourceColElem.CustomWidth; } } private void ClearColumnData(uint col) { var sheetData = _sheet.GetSheetData(); foreach (var row in sheetData.Elements().ToList()) { var cell = FindCellInRow(row, col); cell?.Remove(); } } private Cell? FindCellInRow(Row row, uint colIndex) { foreach (var cell in row.Elements()) { string cellRef = cell.CellReference?.Value ?? string.Empty; if (TryParseCellReference(cellRef, out uint _, out uint colIdx) && colIdx == colIndex) return cell; } return null; } private void InsertCellInRow(Row row, Cell cell, uint colIndex) { string newRef = NumberToColumnLetter(colIndex) + (row.RowIndex?.Value ?? 1).ToString(); cell.CellReference = newRef; // Вставляем в правильном порядке (по возрастанию столбцов) bool inserted = false; foreach (var existing in row.Elements().ToList()) { if (TryParseCellReference(existing.CellReference?.Value ?? string.Empty, out _, out uint existingCol) && existingCol > colIndex) { existing.InsertBeforeSelf(cell); inserted = true; break; } } if (!inserted) row.Append(cell); } private Column? GetColumnElementForIndex(uint col) { var cols = _sheet.Worksheet.GetFirstChild(); if (cols == null) return null; foreach (Column c in cols.Elements()) { if (c.Min?.Value is { } min && c.Max?.Value is { } max && min <= col && max >= col) return c; } return null; } private Column GetOrCreateColumnElementForIndex(uint col) { var existing = GetColumnElementForIndex(col); if (existing != null) return existing; var worksheet = _sheet.Worksheet; var cols = worksheet.GetFirstChild(); if (cols == null) { cols = new Columns(); worksheet.InsertAt(cols, 0); } var newCol = new Column { Min = col, Max = col, Width = 8.43, CustomWidth = true }; cols.Append(newCol); return newCol; } private int GetOrCreateNumberFormatId(NumberFormatPattern format) { return _writer.GetOrCreateCellFormatId(numberFormat: format); } private static bool TryParseCellReference(string reference, out uint row, out uint col) { row = 0; col = 0; if (string.IsNullOrEmpty(reference)) return false; int i = 0; while (i < reference.Length && char.IsLetter(reference[i])) i++; if (i == 0) return false; string colPart = reference.Substring(0, i); string rowPart = reference.Substring(i); if (!uint.TryParse(rowPart, out row)) return false; col = CellAddressHelper.ColumnLetterToIndex(colPart); return true; } private static string NumberToColumnLetter(uint col) { if (col == 0) throw new ArgumentException("Column number must be > 0"); string result = ""; while (col > 0) { col--; result = (char)('A' + (col % 26)) + result; col /= 26; } return result; } }