This commit is contained in:
@@ -5,9 +5,77 @@
|
||||
/// </summary>
|
||||
internal sealed class ExcelCell(ExcelWriter writer, ExcelSheet sheet, uint row, uint col) : ICell
|
||||
{
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Возвращает эффективный стиль ячейки с учётом наследования от строки и столбца.
|
||||
/// </summary>
|
||||
public CellStyle? GetCellStyle()
|
||||
{
|
||||
var cell = GetCellElement();
|
||||
CellStyle? cellStyle = null;
|
||||
|
||||
// 1. Пытаемся получить стиль самой ячейки
|
||||
if (cell?.StyleIndex?.Value is uint cellStyleIndex)
|
||||
{
|
||||
cellStyle = writer.GetCellStyle(cellStyleIndex);
|
||||
if (cellStyle != null && !cellStyle.IsEmpty())
|
||||
return cellStyle; // если у ячейки есть свой стиль, он имеет наивысший приоритет
|
||||
}
|
||||
|
||||
// 2. Стиль строки
|
||||
var sheetData = sheet.GetSheetData();
|
||||
var rowElement = FindRowElement(sheetData, row);
|
||||
if (rowElement?.StyleIndex?.Value is uint rowStyleIndex)
|
||||
{
|
||||
var rowStyle = writer.GetCellStyle(rowStyleIndex);
|
||||
if (rowStyle != null && !rowStyle.IsEmpty())
|
||||
{
|
||||
// Если у ячейки нет стиля, возвращаем стиль строки
|
||||
if (cellStyle == null)
|
||||
return rowStyle;
|
||||
// Иначе объединяем: стиль ячейки имеет приоритет, но некоторые свойства могут быть не заданы
|
||||
// (например, если у ячейки только Border, а у строки Fill, то в результате будет и Border, и Fill)
|
||||
// Объединяем: сначала берём стиль строки, затем накладываем стиль ячейки (перекрывая)
|
||||
return rowStyle.Merge(cellStyle);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Стиль столбца
|
||||
if (col > 0)
|
||||
{
|
||||
var columnElement = ExcelColumn.GetColumnElementInternal(sheet, col);
|
||||
if (columnElement?.Style?.Value is uint colStyleIndex)
|
||||
{
|
||||
var colStyle = writer.GetCellStyle(colStyleIndex);
|
||||
if (colStyle != null && !colStyle.IsEmpty())
|
||||
{
|
||||
// Если нет стиля ячейки и строки, возвращаем стиль столбца
|
||||
if (cellStyle == null)
|
||||
return colStyle;
|
||||
// Иначе объединяем: стиль ячейки + стиль строки (если есть) + стиль столбца
|
||||
// Сначала объединяем стиль строки и столбца, затем накладываем стиль ячейки
|
||||
// Но проще: берём стиль ячейки (если есть) и объединяем со стилем столбца
|
||||
return colStyle.Merge(cellStyle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Если ничего нет, возвращаем null
|
||||
return cellStyle; // может быть null
|
||||
}
|
||||
|
||||
public void ApplyStyle(CellStyle style)
|
||||
{
|
||||
var cell = GetOrCreateCellElement();
|
||||
int styleIndex = writer.GetOrCreateStyleId(style);
|
||||
cell.StyleIndex = (uint)styleIndex;
|
||||
}
|
||||
|
||||
|
||||
// Кэш фрагментов богатого текста (только для InlineString)
|
||||
private List<IRun>? _runsCache;
|
||||
private bool _cacheValid;
|
||||
List<IRun>? _runsCache;
|
||||
bool _cacheValid;
|
||||
|
||||
// ---- Реализация ICell (новые методы) ----
|
||||
|
||||
@@ -96,35 +164,50 @@ internal sealed class ExcelCell(ExcelWriter writer, ExcelSheet sheet, uint row,
|
||||
return TryRemoveRun(index);
|
||||
}
|
||||
|
||||
public ICell Break()
|
||||
{
|
||||
EnsureCacheValid();
|
||||
if (_runsCache != null && _runsCache.Count > 0)
|
||||
{
|
||||
var lastRun = _runsCache[_runsCache.Count - 1];
|
||||
lastRun.Text += "\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
Run("\n", null);
|
||||
}
|
||||
_cacheValid = true;
|
||||
UpdateCellFromCache();
|
||||
return this;
|
||||
}
|
||||
public ICell Break()
|
||||
{
|
||||
EnsureCacheValid();
|
||||
if (_runsCache != null && _runsCache.Count > 0)
|
||||
{
|
||||
var lastRun = _runsCache[_runsCache.Count - 1];
|
||||
lastRun.Text += "\n";
|
||||
EnsureWrapTextEnabled();
|
||||
}
|
||||
else
|
||||
{
|
||||
Run("\n", null);
|
||||
}
|
||||
_cacheValid = true;
|
||||
UpdateCellFromCache();
|
||||
return this;
|
||||
}
|
||||
|
||||
public ICell Run(string text, RunFormat? format = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(text)) return this;
|
||||
EnsureCacheValid();
|
||||
_runsCache ??= [];
|
||||
_runsCache.Add(new ExcelRun { Text = text, Format = format });
|
||||
_cacheValid = true;
|
||||
UpdateCellFromCache();
|
||||
return this;
|
||||
}
|
||||
public ICell Run(string text, RunFormat? format = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(text)) return this;
|
||||
EnsureCacheValid();
|
||||
_runsCache ??= [];
|
||||
_runsCache.Add(new ExcelRun { Text = text, Format = format });
|
||||
_cacheValid = true;
|
||||
UpdateCellFromCache();
|
||||
|
||||
public ICell RunBreak(string text, RunFormat? format = null)
|
||||
if (text.Contains('\n'))
|
||||
EnsureWrapTextEnabled();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private void EnsureWrapTextEnabled()
|
||||
{
|
||||
var currentStyle = GetCellStyle() ?? new CellStyle();
|
||||
if (currentStyle.Align is null || currentStyle.Align.Value.WrapText != true)
|
||||
{
|
||||
var align = (currentStyle.Align ?? new CellAlign()) with { WrapText = true };
|
||||
Set(align); // вызовет объединение через TryMerge
|
||||
}
|
||||
}
|
||||
|
||||
public ICell RunBreak(string text, RunFormat? format = null)
|
||||
{
|
||||
Run(text, format);
|
||||
Break();
|
||||
@@ -307,7 +390,7 @@ internal sealed class ExcelCell(ExcelWriter writer, ExcelSheet sheet, uint row,
|
||||
|
||||
// ---- Приватные методы для работы с кэшем ----
|
||||
|
||||
private void EnsureCacheValid()
|
||||
void EnsureCacheValid()
|
||||
{
|
||||
if (_cacheValid) return;
|
||||
lock (writer._syncLock)
|
||||
@@ -338,7 +421,7 @@ internal sealed class ExcelCell(ExcelWriter writer, ExcelSheet sheet, uint row,
|
||||
}
|
||||
}
|
||||
|
||||
private string GetStringFromCell(Cell cell)
|
||||
string GetStringFromCell(Cell cell)
|
||||
{
|
||||
if (cell == null) return string.Empty;
|
||||
if (cell.DataType?.Value == CellValues.SharedString && cell.CellValue != null)
|
||||
@@ -352,7 +435,7 @@ internal sealed class ExcelCell(ExcelWriter writer, ExcelSheet sheet, uint row,
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
private void UpdateCellFromCache()
|
||||
void UpdateCellFromCache()
|
||||
{
|
||||
lock (writer._syncLock)
|
||||
{
|
||||
@@ -375,61 +458,66 @@ internal sealed class ExcelCell(ExcelWriter writer, ExcelSheet sheet, uint row,
|
||||
}
|
||||
}
|
||||
|
||||
private InlineString BuildInlineStringFromCache()
|
||||
{
|
||||
var inline = new InlineString();
|
||||
if (_runsCache == null) return inline;
|
||||
foreach (var run in _runsCache)
|
||||
{
|
||||
var runElement = new Run();
|
||||
runElement.Append(new Text(run.Text));
|
||||
if (run.Format is { } fmt)
|
||||
{
|
||||
var rPr = new RunProperties();
|
||||
if (fmt.IsBold == true) rPr.Append(new Bold());
|
||||
if (fmt.IsItalic == true) rPr.Append(new Italic());
|
||||
if (fmt.Underline.HasValue)
|
||||
{
|
||||
rPr.Append(new Underline
|
||||
{
|
||||
Val = fmt.Underline.Value switch
|
||||
{
|
||||
UnderlineStyle.Single => UnderlineValues.Single,
|
||||
UnderlineStyle.Double => UnderlineValues.Double,
|
||||
UnderlineStyle.SingleAccounting => UnderlineValues.SingleAccounting,
|
||||
UnderlineStyle.DoubleAccounting => UnderlineValues.DoubleAccounting,
|
||||
_ => throw new NotImplementedException(),
|
||||
}
|
||||
});
|
||||
}
|
||||
if (fmt.IsStrike == true) rPr.Append(new Strike());
|
||||
if (fmt.Color.HasValue)
|
||||
{
|
||||
var excelColor = fmt.Color.Value.ToExcel();
|
||||
rPr.Append(excelColor);
|
||||
}
|
||||
if (fmt.FontSize.HasValue)
|
||||
rPr.Append(new FontSize { Val = fmt.FontSize.Value });
|
||||
if (!string.IsNullOrEmpty(fmt.FontFamily))
|
||||
rPr.Append(new RunFont { Val = fmt.FontFamily });
|
||||
if (fmt.Vertical.HasValue)
|
||||
{
|
||||
var vertAlign = new VerticalTextAlignment
|
||||
{
|
||||
Val = fmt.Vertical.Value == VerticalTextRunAlignment.Superscript
|
||||
? VerticalAlignmentRunValues.Superscript
|
||||
: VerticalAlignmentRunValues.Subscript
|
||||
};
|
||||
rPr.Append(vertAlign);
|
||||
}
|
||||
runElement.RunProperties = rPr;
|
||||
}
|
||||
inline.Append(runElement);
|
||||
}
|
||||
return inline;
|
||||
}
|
||||
InlineString BuildInlineStringFromCache()
|
||||
{
|
||||
var inline = new InlineString();
|
||||
if (_runsCache == null) return inline;
|
||||
foreach (var run in _runsCache)
|
||||
{
|
||||
var runElement = new Run();
|
||||
var textElement = new Text(run.Text);
|
||||
// Устанавливаем preserve, если текст содержит пробелы или переносы
|
||||
if (run.Text.Any(c => char.IsWhiteSpace(c)))
|
||||
textElement.Space = SpaceProcessingModeValues.Preserve;
|
||||
runElement.Append(textElement);
|
||||
|
||||
private static RunFormat MergeRunFormat(RunFormat baseFmt, RunFormat overlay)
|
||||
if (run.Format is { } fmt)
|
||||
{
|
||||
var rPr = new RunProperties();
|
||||
if (fmt.IsBold == true)
|
||||
rPr.Append(new Bold());
|
||||
if (fmt.IsItalic == true)
|
||||
rPr.Append(new Italic());
|
||||
if (fmt.Underline.HasValue)
|
||||
{
|
||||
rPr.Append(new Underline
|
||||
{
|
||||
Val = fmt.Underline.Value switch
|
||||
{
|
||||
UnderlineStyle.Single => UnderlineValues.Single,
|
||||
UnderlineStyle.Double => UnderlineValues.Double,
|
||||
UnderlineStyle.SingleAccounting => UnderlineValues.SingleAccounting,
|
||||
UnderlineStyle.DoubleAccounting => UnderlineValues.DoubleAccounting,
|
||||
_ => throw new NotImplementedException(),
|
||||
}
|
||||
});
|
||||
}
|
||||
if (fmt.IsStrike == true)
|
||||
rPr.Append(new Strike());
|
||||
if (fmt.TryGetExcelColor(out var c))
|
||||
rPr.Append(c);
|
||||
if (fmt.FontSize.HasValue)
|
||||
rPr.Append(new FontSize { Val = fmt.FontSize.Value });
|
||||
if (!string.IsNullOrEmpty(fmt.FontFamily))
|
||||
rPr.Append(new RunFont { Val = fmt.FontFamily });
|
||||
if (fmt.Vertical.HasValue)
|
||||
{
|
||||
var vertAlign = new VerticalTextAlignment
|
||||
{
|
||||
Val = fmt.Vertical.Value == VerticalTextRunAlignment.Superscript
|
||||
? VerticalAlignmentRunValues.Superscript
|
||||
: VerticalAlignmentRunValues.Subscript
|
||||
};
|
||||
rPr.Append(vertAlign);
|
||||
}
|
||||
runElement.RunProperties = rPr;
|
||||
}
|
||||
inline.Append(runElement);
|
||||
}
|
||||
return inline;
|
||||
}
|
||||
|
||||
static RunFormat MergeRunFormat(RunFormat baseFmt, RunFormat overlay)
|
||||
{
|
||||
return new RunFormat
|
||||
{
|
||||
@@ -709,7 +797,7 @@ internal sealed class ExcelCell(ExcelWriter writer, ExcelSheet sheet, uint row,
|
||||
|
||||
public double? TryGetNumber() => TryGetNumber(out double v) ? v : null;
|
||||
|
||||
public bool TrySet(string formula, NumberFormatPattern? format = null)
|
||||
public bool TrySetFormula(string formula, NumberFormatPattern? format = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(formula)) return false;
|
||||
writer.ThrowIfDisposed();
|
||||
@@ -718,47 +806,52 @@ internal sealed class ExcelCell(ExcelWriter writer, ExcelSheet sheet, uint row,
|
||||
var cell = GetOrCreateCellElement();
|
||||
cell.CellFormula = new CellFormula(formula);
|
||||
cell.DataType = null; // формула сама определяет тип
|
||||
if (format != null)
|
||||
SetNumberFormatInternal(cell, format);
|
||||
return true;
|
||||
if (format != null) Set(cell, format);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public ICell Set(string formula, NumberFormatPattern? format = null)
|
||||
public ICell SetFormula(string formula, NumberFormatPattern? format = null)
|
||||
{
|
||||
if (!TrySet(formula, format))
|
||||
if (!TrySetFormula(formula, format))
|
||||
throw new InvalidOperationException("Failed to set formula");
|
||||
return this;
|
||||
}
|
||||
|
||||
public ICell Set(NumberFormatPattern format)
|
||||
{
|
||||
if (format == null) return this;
|
||||
writer.ThrowIfDisposed();
|
||||
lock (writer._syncLock)
|
||||
{
|
||||
var cell = GetOrCreateCellElement();
|
||||
SetNumberFormatInternal(cell, format);
|
||||
var currentStyle = GetCellStyle() ?? new CellStyle();
|
||||
if (currentStyle.TryMerge(format, out var newStyle))
|
||||
ApplyStyle(newStyle);
|
||||
return this;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ICell Set(CellAlign align)
|
||||
public ICell Set(Cell cell, NumberFormatPattern format)
|
||||
{
|
||||
writer.ThrowIfDisposed();
|
||||
lock (writer._syncLock)
|
||||
{
|
||||
var currentStyle = GetCellStyle() ?? new CellStyle();
|
||||
if (currentStyle.TryMerge(format, out var newStyle))
|
||||
cell.StyleIndex = (uint)writer.GetOrCreateStyleId(newStyle);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ICell Set(CellAlign align)
|
||||
{
|
||||
writer.ThrowIfDisposed();
|
||||
lock (writer._syncLock)
|
||||
{
|
||||
var cell = GetOrCreateCellElement();
|
||||
int styleIndex = writer.GetOrCreateCellFormatId(
|
||||
numberFormat: null,
|
||||
font: null,
|
||||
fill: null,
|
||||
border: null,
|
||||
align: align
|
||||
);
|
||||
cell.StyleIndex = (uint)styleIndex;
|
||||
return this;
|
||||
var currentStyle = GetCellStyle() ?? new CellStyle();
|
||||
if (currentStyle.TryMerge(align, out var newStyle))
|
||||
ApplyStyle(newStyle);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -768,15 +861,39 @@ internal sealed class ExcelCell(ExcelWriter writer, ExcelSheet sheet, uint row,
|
||||
writer.ThrowIfDisposed();
|
||||
lock (writer._syncLock)
|
||||
{
|
||||
var cell = GetOrCreateCellElement();
|
||||
int styleIndex = writer.GetOrCreateCellFormatId(
|
||||
numberFormat: null,
|
||||
font: null,
|
||||
fill: null,
|
||||
border: border,
|
||||
align: null
|
||||
);
|
||||
cell.StyleIndex = (uint)styleIndex;
|
||||
// 1. Применяем границу к текущей ячейке (объединяя с существующей)
|
||||
var currentStyle = GetCellStyle() ?? new CellStyle();
|
||||
CellBorder mergedBorder;
|
||||
bool changed;
|
||||
if (currentStyle.Border is { } currBorder)
|
||||
changed = currBorder.TryMerge(border, out mergedBorder);
|
||||
else
|
||||
{
|
||||
mergedBorder = border;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (!changed)
|
||||
return this;
|
||||
|
||||
ApplyStyle(currentStyle with { Border = mergedBorder });
|
||||
|
||||
// 2. Каскадное обновление соседей
|
||||
// Для каждой стороны, которая была установлена (не null), очищаем соответствующую сторону у соседа
|
||||
if (border.TopBorder.HasValue)
|
||||
ClearNeighborBorder(-1, 0, b => b with { BottomBorder = null });
|
||||
|
||||
if (border.BottomBorder.HasValue)
|
||||
ClearNeighborBorder(1, 0, b => b with { TopBorder = null });
|
||||
|
||||
if (border.LeftBorder.HasValue)
|
||||
ClearNeighborBorder(0, -1, b => b with { RightBorder = null });
|
||||
|
||||
if (border.RightBorder.HasValue)
|
||||
ClearNeighborBorder(0, 1, b => b with { LeftBorder = null });
|
||||
|
||||
// Диагональные границы не влияют на соседей, их не очищаем
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -787,16 +904,10 @@ internal sealed class ExcelCell(ExcelWriter writer, ExcelSheet sheet, uint row,
|
||||
writer.ThrowIfDisposed();
|
||||
lock (writer._syncLock)
|
||||
{
|
||||
var cell = GetOrCreateCellElement();
|
||||
int styleIndex = writer.GetOrCreateCellFormatId(
|
||||
numberFormat: null,
|
||||
font: null,
|
||||
fill: fill,
|
||||
border: null,
|
||||
align: null
|
||||
);
|
||||
cell.StyleIndex = (uint)styleIndex;
|
||||
return this;
|
||||
var currentStyle = GetCellStyle() ?? new CellStyle();
|
||||
if (currentStyle.TryMerge(fill, out var newStyle))
|
||||
ApplyStyle(newStyle);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -806,19 +917,148 @@ internal sealed class ExcelCell(ExcelWriter writer, ExcelSheet sheet, uint row,
|
||||
writer.ThrowIfDisposed();
|
||||
lock (writer._syncLock)
|
||||
{
|
||||
var cell = GetOrCreateCellElement();
|
||||
int styleIndex = writer.GetOrCreateCellFormatId(
|
||||
numberFormat: null,
|
||||
font: font,
|
||||
fill: null,
|
||||
border: null,
|
||||
align: null
|
||||
);
|
||||
cell.StyleIndex = (uint)styleIndex;
|
||||
var currentStyle = GetCellStyle() ?? new CellStyle();
|
||||
if (currentStyle.TryMerge(font, out var newStyle))
|
||||
ApplyStyle(newStyle);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ICell Set(CellStyle style)
|
||||
{
|
||||
if (style is null) return this;
|
||||
writer.ThrowIfDisposed();
|
||||
lock (writer._syncLock)
|
||||
{
|
||||
var currentStyle = GetCellStyle() ?? new CellStyle();
|
||||
if (currentStyle.TryMerge(style, out style))
|
||||
ApplyStyle(style);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void ClearNeighborBorder(int rowOffset, int colOffset, Func<CellBorder, CellBorder> clearFunc)
|
||||
{
|
||||
var neighbor = GetNeighbor(rowOffset, colOffset);
|
||||
if (neighbor is not { } neighbr)
|
||||
return;
|
||||
|
||||
var neighborBorder = neighbr.GetCellBorder();
|
||||
var newBorder = clearFunc(neighborBorder);
|
||||
// Если после очистки граница изменилась, применяем изолированно
|
||||
if (!neighborBorder.Equals(newBorder))
|
||||
neighbr.SetBorderIsolate(newBorder);
|
||||
}
|
||||
internal void SetBorderIsolate(CellBorder border)
|
||||
{
|
||||
var currentStyle = GetCellStyle() ?? new CellStyle();
|
||||
if (currentStyle.TryMerge(border, out var newStyle))
|
||||
ApplyStyle(newStyle);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Возвращает соседнюю ячейку по указанному смещению.
|
||||
/// </summary>
|
||||
/// <param name="cell">Исходная ячейка.</param>
|
||||
/// <param name="rowOffset">Смещение по строкам (положительное – вниз).</param>
|
||||
/// <param name="colOffset">Смещение по столбцам (положительное – вправо).</param>
|
||||
/// <returns>Соседняя ячейка, или null, если она выходит за пределы листа.</returns>
|
||||
public ExcelCell? GetNeighbor(int rowOffset, int colOffset)
|
||||
{
|
||||
// Проверяем, что смещение не равно нулю и координаты не выходят за допустимые пределы (хотя мы не знаем границ листа)
|
||||
if (rowOffset == 0 && colOffset == 0) return null;
|
||||
int newRow = (int)row + rowOffset;
|
||||
int newCol = (int)col + colOffset;
|
||||
if (newRow < 1 || newCol < 1) return null;
|
||||
// Excel допускает до 1048576 строк и 16384 столбцов (но мы не будем жестко ограничивать)
|
||||
// Просто создаём объект ячейки, даже если она не существует физически.
|
||||
return new ExcelCell(writer, sheet, (uint)newRow, (uint)newCol);
|
||||
}
|
||||
|
||||
internal ICell SetBorderOverride(CellBorder border)
|
||||
{
|
||||
writer.ThrowIfDisposed();
|
||||
lock (writer._syncLock)
|
||||
{
|
||||
// Определяем, какие стороны заданы в border
|
||||
bool hasTop = border.TopBorder.HasValue;
|
||||
bool hasBottom = border.BottomBorder.HasValue;
|
||||
bool hasLeft = border.LeftBorder.HasValue;
|
||||
bool hasRight = border.RightBorder.HasValue;
|
||||
|
||||
// Если какая-то сторона задана, то для соседней ячейки на этой стороне мы должны очистить противоположную сторону.
|
||||
// Например, если мы устанавливаем верхнюю границу у текущей ячейки, то у ячейки сверху нужно очистить нижнюю границу.
|
||||
// Аналогично для остальных сторон.
|
||||
|
||||
if (hasTop)
|
||||
{
|
||||
var neighbor = GetNeighbor(-1, 0);
|
||||
if (neighbor != null)
|
||||
{
|
||||
var neighborBorder = neighbor.GetCellBorder();
|
||||
if (neighborBorder.BottomBorder.HasValue)
|
||||
{
|
||||
var newNeighborBorder = neighborBorder with { BottomBorder = null };
|
||||
// Применяем только границы, не затрагивая другие аспекты стиля
|
||||
((ExcelCell)neighbor).SetBorderIsolate(newNeighborBorder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasBottom)
|
||||
{
|
||||
var neighbor = GetNeighbor(1, 0);
|
||||
if (neighbor != null)
|
||||
{
|
||||
var neighborBorder = neighbor.GetCellBorder();
|
||||
if (neighborBorder.TopBorder.HasValue)
|
||||
{
|
||||
var newNeighborBorder = neighborBorder with { TopBorder = null };
|
||||
((ExcelCell)neighbor).SetBorderIsolate(newNeighborBorder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasLeft)
|
||||
{
|
||||
var neighbor = GetNeighbor(0, -1);
|
||||
if (neighbor != null)
|
||||
{
|
||||
var neighborBorder = neighbor.GetCellBorder();
|
||||
if (neighborBorder.RightBorder.HasValue)
|
||||
{
|
||||
var newNeighborBorder = neighborBorder with { RightBorder = null };
|
||||
((ExcelCell)neighbor).SetBorderIsolate(newNeighborBorder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasRight)
|
||||
{
|
||||
var neighbor = GetNeighbor(0, 1);
|
||||
if (neighbor != null)
|
||||
{
|
||||
var neighborBorder = neighbor.GetCellBorder();
|
||||
if (neighborBorder.LeftBorder.HasValue)
|
||||
{
|
||||
var newNeighborBorder = neighborBorder with { LeftBorder = null };
|
||||
((ExcelCell)neighbor).SetBorderIsolate(newNeighborBorder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Теперь устанавливаем границу для текущей ячейки (изолированно, чтобы не было зацикливания)
|
||||
((ExcelCell)this).SetBorderIsolate(border);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public ICell Set(bool value)
|
||||
{
|
||||
writer.ThrowIfDisposed();
|
||||
@@ -849,9 +1089,10 @@ internal sealed class ExcelCell(ExcelWriter writer, ExcelSheet sheet, uint row,
|
||||
var cell = GetOrCreateCellElement();
|
||||
cell.DataType = CellValues.Number;
|
||||
cell.CellValue = new CellValue(value.ToString(CultureInfo.InvariantCulture));
|
||||
if (format != null)
|
||||
SetNumberFormatInternal(cell, format);
|
||||
}
|
||||
cell.InlineString = null;
|
||||
cell.CellFormula = null;
|
||||
if (format != null) Set(cell, format);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
public ICell Set(float value, NumberFormatPattern? format = null) => Set((double)value, format);
|
||||
@@ -897,9 +1138,9 @@ internal sealed class ExcelCell(ExcelWriter writer, ExcelSheet sheet, uint row,
|
||||
public ICell CopyTo(uint rowIndex, string colIndex, out ICell copiedCell) =>
|
||||
CopyTo(rowIndex, CellAddressHelper.ColumnLetterToIndex(colIndex), out copiedCell);
|
||||
|
||||
// Private helpers
|
||||
// helpers
|
||||
|
||||
private Cell? GetCellElement()
|
||||
Cell? GetCellElement()
|
||||
{
|
||||
var sheetData = sheet.GetSheetData();
|
||||
var eRow = FindRowElement(sheetData, row);
|
||||
@@ -907,27 +1148,37 @@ internal sealed class ExcelCell(ExcelWriter writer, ExcelSheet sheet, uint row,
|
||||
return FindCellInRow(eRow, col);
|
||||
}
|
||||
|
||||
private Cell GetOrCreateCellElement()
|
||||
{
|
||||
var sheetData = sheet.GetSheetData();
|
||||
var eRow = GetOrCreateRowElement(sheetData, row);
|
||||
var cell = FindCellInRow(eRow, col);
|
||||
if (cell != null) return cell;
|
||||
cell = new Cell();
|
||||
string cellRef = CellAddressHelper.ColumnIndexToLetter(col) + row.ToString();
|
||||
cell.CellReference = cellRef;
|
||||
InsertCellInRow(eRow, cell, col);
|
||||
return cell;
|
||||
}
|
||||
private Cell GetOrCreateCellElement()
|
||||
{
|
||||
var sheetData = sheet.GetSheetData();
|
||||
var rowElement = GetOrCreateRowElement(sheetData, row);
|
||||
var cell = FindCellInRow(rowElement, col);
|
||||
if (cell != null) return cell;
|
||||
|
||||
private static Row? FindRowElement(SheetData sheetData, uint rowIndex)
|
||||
cell = new Cell();
|
||||
string cellRef = CellAddressHelper.ColumnIndexToLetter(col) + row.ToString();
|
||||
cell.CellReference = cellRef;
|
||||
InsertCellInRow(rowElement, cell, col);
|
||||
|
||||
// Наследование стиля
|
||||
var inheritedStyle = GetCellStyle(); // теперь этот метод учитывает строку и столбец
|
||||
if (inheritedStyle != null && !inheritedStyle.IsEmpty())
|
||||
{
|
||||
int styleIndex = writer.GetOrCreateStyleId(inheritedStyle);
|
||||
cell.StyleIndex = (uint)styleIndex;
|
||||
}
|
||||
|
||||
return cell;
|
||||
}
|
||||
|
||||
static Row? FindRowElement(SheetData sheetData, uint rowIndex)
|
||||
{
|
||||
foreach (var eRow in sheetData.Elements<Row>())
|
||||
if (eRow.RowIndex?.Value == rowIndex) return eRow;
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Row GetOrCreateRowElement(SheetData sheetData, uint rowIndex)
|
||||
static Row GetOrCreateRowElement(SheetData sheetData, uint rowIndex)
|
||||
{
|
||||
var existing = FindRowElement(sheetData, rowIndex);
|
||||
if (existing != null) return existing;
|
||||
@@ -936,7 +1187,7 @@ internal sealed class ExcelCell(ExcelWriter writer, ExcelSheet sheet, uint row,
|
||||
return newRow;
|
||||
}
|
||||
|
||||
private static void InsertRowElement(SheetData sheetData, Row eRow, uint rowIndex)
|
||||
static void InsertRowElement(SheetData sheetData, Row eRow, uint rowIndex)
|
||||
{
|
||||
bool inserted = false;
|
||||
foreach (var existing in sheetData.Elements<Row>().ToList())
|
||||
@@ -951,7 +1202,7 @@ internal sealed class ExcelCell(ExcelWriter writer, ExcelSheet sheet, uint row,
|
||||
if (!inserted) sheetData.Append(eRow);
|
||||
}
|
||||
|
||||
private static Cell? FindCellInRow(Row eRow, uint colIndex)
|
||||
static Cell? FindCellInRow(Row eRow, uint colIndex)
|
||||
{
|
||||
foreach (var cell in eRow.Elements<Cell>())
|
||||
{
|
||||
@@ -961,7 +1212,7 @@ internal sealed class ExcelCell(ExcelWriter writer, ExcelSheet sheet, uint row,
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void InsertCellInRow(Row eRow, Cell cell, uint colIndex)
|
||||
static void InsertCellInRow(Row eRow, Cell cell, uint colIndex)
|
||||
{
|
||||
string newRef = CellAddressHelper.ColumnIndexToLetter(colIndex) + (eRow.RowIndex?.Value ?? 1).ToString();
|
||||
cell.CellReference = newRef;
|
||||
@@ -978,37 +1229,19 @@ internal sealed class ExcelCell(ExcelWriter writer, ExcelSheet sheet, uint row,
|
||||
if (!inserted) eRow.Append(cell);
|
||||
}
|
||||
|
||||
private void InsertCellAt(uint rowIndex, uint colIndex, Cell cell)
|
||||
void InsertCellAt(uint rowIndex, uint colIndex, Cell cell)
|
||||
{
|
||||
var sheetData = sheet.GetSheetData();
|
||||
var row = GetOrCreateRowElement(sheetData, rowIndex);
|
||||
InsertCellInRow(row, cell, colIndex);
|
||||
}
|
||||
|
||||
private void SetNumberFormatInternal(Cell cell, NumberFormatPattern format)
|
||||
{
|
||||
if (format == null) return;
|
||||
int styleIndex = writer.GetOrCreateCellFormatId(
|
||||
numberFormat: format,
|
||||
font: null,
|
||||
fill: null,
|
||||
border: null,
|
||||
align: null
|
||||
);
|
||||
cell.StyleIndex = (uint)styleIndex;
|
||||
}
|
||||
|
||||
private string GetSharedString(uint index)
|
||||
string GetSharedString(uint index)
|
||||
{
|
||||
return writer.GetSharedString(index);
|
||||
}
|
||||
|
||||
private int GetOrAddSharedString(string value)
|
||||
{
|
||||
return writer.GetOrAddSharedString(value);
|
||||
}
|
||||
|
||||
private string ExtractTextFromInlineString(InlineString? inlineString)
|
||||
string ExtractTextFromInlineString(InlineString? inlineString)
|
||||
{
|
||||
if (inlineString == null) return string.Empty;
|
||||
var sb = new System.Text.StringBuilder();
|
||||
@@ -1020,10 +1253,10 @@ internal sealed class ExcelCell(ExcelWriter writer, ExcelSheet sheet, uint row,
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private MergeCells? GetMergeCells() =>
|
||||
MergeCells? GetMergeCells() =>
|
||||
sheet.Worksheet.GetFirstChild<MergeCells>();
|
||||
|
||||
private bool TryParseRangeReference(string reference, out ExcelRange range)
|
||||
bool TryParseRangeReference(string reference, out ExcelRange range)
|
||||
{
|
||||
range = null!;
|
||||
if (string.IsNullOrEmpty(reference)) return false;
|
||||
|
||||
Reference in New Issue
Block a user