Добавьте файлы проекта.
This commit is contained in:
502
QWERTYkez.ExcelProcessor/Editors/ExcelColumn.cs
Normal file
502
QWERTYkez.ExcelProcessor/Editors/ExcelColumn.cs
Normal file
@@ -0,0 +1,502 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user