502 lines
15 KiB
C#
502 lines
15 KiB
C#
namespace QWERTYkez.ExcelProcessor.Editors;
|
|
|
|
/// <summary>
|
|
/// Внутренняя реализация <see cref="IColumn"/>.
|
|
/// </summary>
|
|
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);
|
|
|
|
|
|
|
|
/// <summary>Устанавливает ширину столбца без создания объекта IColumn.</summary>
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// <summary>Возвращает ширину столбца.</summary>
|
|
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<Columns>();
|
|
if (cols == null) return null;
|
|
foreach (Column col in cols.Elements<Column>())
|
|
{
|
|
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<Columns>();
|
|
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<Row>())
|
|
{
|
|
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<ICell> 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<Row>())
|
|
{
|
|
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<Columns>();
|
|
if (cols == null) return null;
|
|
foreach (Column col in cols.Elements<Column>())
|
|
{
|
|
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<Columns>();
|
|
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<Row>())
|
|
{
|
|
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<Row>().ToList())
|
|
{
|
|
var cell = FindCellInRow(row, col);
|
|
cell?.Remove();
|
|
}
|
|
}
|
|
|
|
private Cell? FindCellInRow(Row row, uint colIndex)
|
|
{
|
|
foreach (var cell in row.Elements<Cell>())
|
|
{
|
|
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<Cell>().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<Columns>();
|
|
if (cols == null) return null;
|
|
foreach (Column c in cols.Elements<Column>())
|
|
{
|
|
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<Columns>();
|
|
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;
|
|
}
|
|
} |