namespace QWERTYkez.ExcelProcessor;
///
/// Определяет границы ячейки: верхнюю, нижнюю, левую, правую и диагональные.
/// Каждая граница может иметь стиль и цвет.
///
public readonly struct CellBorder : IEquatable
{
public static CellBorder Bottom { get; } = new() { BottomBorder = BorderSide.BlackThin };
public static CellBorder Top { get; } = new() { TopBorder = BorderSide.BlackThin };
public static CellBorder Left { get; } = new() { LeftBorder = BorderSide.BlackThin };
public static CellBorder Right { get; } = new() { RightBorder = BorderSide.BlackThin };
public static CellBorder All { get; } = new()
{
BottomBorder = BorderSide.BlackThin,
TopBorder = BorderSide.BlackThin,
LeftBorder = BorderSide.BlackThin,
RightBorder = BorderSide.BlackThin
};
/// Верхняя граница.
public BorderSide? TopBorder { get; init; }
/// Нижняя граница.
public BorderSide? BottomBorder { get; init; }
/// Левая граница.
public BorderSide? LeftBorder { get; init; }
/// Правая граница.
public BorderSide? RightBorder { get; init; }
/// Диагональная граница «из левого верхнего в правый нижний» (\\).
public BorderSide? DiagonalLeft { get; init; }
/// Диагональная граница «из левого нижнего в правый верхний» (//).
public BorderSide? DiagonalRight { get; init; }
public bool TryMerge(CellBorder other, out CellBorder result)
{
if (other.TopBorder == TopBorder &&
other.BottomBorder == BottomBorder &&
other.LeftBorder == LeftBorder &&
other.RightBorder == RightBorder &&
other.DiagonalLeft == DiagonalLeft &&
other.DiagonalRight == DiagonalRight)
{
result = default;
return false;
}
result = new CellBorder
{
TopBorder = other.TopBorder ?? TopBorder,
BottomBorder = other.BottomBorder ?? BottomBorder,
LeftBorder = other.LeftBorder ?? LeftBorder,
RightBorder = other.RightBorder ?? RightBorder,
DiagonalLeft = other.DiagonalLeft ?? DiagonalLeft,
DiagonalRight = other.DiagonalRight ?? DiagonalRight
};
return true;
}
/// Создаёт элемент Border для Open XML.
public Border? ToBorder()
{
bool hasAny = TopBorder.HasValue || BottomBorder.HasValue || LeftBorder.HasValue ||
RightBorder.HasValue || DiagonalLeft.HasValue || DiagonalRight.HasValue;
if (!hasAny) return null;
var border = new Border();
if (TopBorder.HasValue)
border.TopBorder = TopBorder.Value.ToBorderElement();
if (BottomBorder.HasValue)
border.BottomBorder = BottomBorder.Value.ToBorderElement();
if (LeftBorder.HasValue)
border.LeftBorder = LeftBorder.Value.ToBorderElement();
if (RightBorder.HasValue)
border.RightBorder = RightBorder.Value.ToBorderElement();
// Обработка диагональных границ
if (DiagonalLeft.HasValue || DiagonalRight.HasValue)
{
// Диагональ \\ (из левого верхнего в правый нижний) = DiagonalDown
border.DiagonalDown = DiagonalLeft.HasValue;
// Диагональ // (из левого нижнего в правый верхний) = DiagonalUp
border.DiagonalUp = DiagonalRight.HasValue;
// Стиль и цвет берём из первой заданной диагонали (например, DiagonalLeft)
var diagSide = (DiagonalLeft ?? DiagonalRight)!.Value;
border.DiagonalBorder = diagSide.ToBorderElement();
}
return border;
}
/// Создаёт CellBorder из элемента Border Open XML.
public static CellBorder FromBorder(Border? border)
{
if (border == null)
return default;
var result = new CellBorder
{
TopBorder = BorderSide.FromBorderProperties(border.TopBorder),
BottomBorder = BorderSide.FromBorderProperties(border.BottomBorder),
LeftBorder = BorderSide.FromBorderProperties(border.LeftBorder),
RightBorder = BorderSide.FromBorderProperties(border.RightBorder)
};
// Диагональные границы
if (border.DiagonalDown?.Value == true)
result = result with { DiagonalLeft = BorderSide.FromBorderProperties(border.DiagonalBorder) };
if (border.DiagonalUp?.Value == true)
result = result with { DiagonalRight = BorderSide.FromBorderProperties(border.DiagonalBorder) };
return result;
}
public override bool Equals(object? obj) => obj is CellBorder other && Equals(other);
public bool Equals(CellBorder other) => this == other;
public static bool operator ==(CellBorder left, CellBorder right) =>
Equals(left.TopBorder, right.TopBorder) &&
Equals(left.BottomBorder, right.BottomBorder) &&
Equals(left.LeftBorder, right.LeftBorder) &&
Equals(left.RightBorder, right.RightBorder) &&
Equals(left.DiagonalLeft, right.DiagonalLeft) &&
Equals(left.DiagonalRight, right.DiagonalRight);
public static bool operator !=(CellBorder left, CellBorder right) => !(left == right);
public override int GetHashCode()
{
unchecked
{
int hash = 17;
hash = hash * 31 + (TopBorder?.GetHashCode() ?? 0);
hash = hash * 31 + (BottomBorder?.GetHashCode() ?? 0);
hash = hash * 31 + (LeftBorder?.GetHashCode() ?? 0);
hash = hash * 31 + (RightBorder?.GetHashCode() ?? 0);
hash = hash * 31 + (DiagonalLeft?.GetHashCode() ?? 0);
hash = hash * 31 + (DiagonalRight?.GetHashCode() ?? 0);
return hash;
}
}
}
/// Стиль и цвет границы.
public readonly struct BorderSide : IEquatable
{
/// Тонкая черная линия
public static BorderSide BlackThin { get; } = new() { Color = System.Drawing.Color.Black, Style = BorderStyle.Thin };
/// Толстая черная линия
public static BorderSide BlackThick { get; } = new() { Color = System.Drawing.Color.Black, Style = BorderStyle.Thick };
/// Средняя черная линия
public static BorderSide BlackMedium { get; } = new() { Color = System.Drawing.Color.Black, Style = BorderStyle.Medium };
/// Стиль линии границы.
public BorderStyle? Style { get; init; }
/// Цвет границы.
public System.Drawing.Color? Color { get; init; }
internal T ToBorderElement() where T : BorderPropertiesType, new()
{
var element = new T();
if (Style.HasValue)
{
element.Style = Style.Value switch
{
BorderStyle.Thin => BorderStyleValues.Thin,
BorderStyle.Medium => BorderStyleValues.Medium,
BorderStyle.Dashed => BorderStyleValues.Dashed,
BorderStyle.Dotted => BorderStyleValues.Dotted,
BorderStyle.Thick => BorderStyleValues.Thick,
BorderStyle.Double => BorderStyleValues.Double,
BorderStyle.Hair => BorderStyleValues.Hair,
BorderStyle.MediumDashed => BorderStyleValues.MediumDashed,
BorderStyle.DashDot => BorderStyleValues.DashDot,
BorderStyle.MediumDashDot => BorderStyleValues.MediumDashDot,
BorderStyle.DashDotDot => BorderStyleValues.DashDotDot,
BorderStyle.MediumDashDotDot => BorderStyleValues.MediumDashDotDot,
BorderStyle.SlantDashDot => BorderStyleValues.SlantDashDot,
_ => throw new NotImplementedException(),
};
}
if (Color.HasValue)
{
var c = Color.Value;
element.Color = new Color { Rgb = $"{c.R:X2}{c.G:X2}{c.B:X2}" };
}
return element;
}
/// Создаёт BorderSide из элемента границы Open XML (TopBorder, BottomBorder, LeftBorder, RightBorder, DiagonalBorder).
public static BorderSide FromBorderProperties(BorderPropertiesType? borderElement)
{
if (borderElement is null)
return default;
var result = new BorderSide();
if (borderElement.Style is not null)
{
result = result with { Style = MapBorderStyleFromExcel(borderElement.Style.Value) };
}
if (borderElement.Color?.Rgb?.Value is { } rgb && rgb.Length >= 6)
{
result = result with
{
Color = System.Drawing.Color.FromArgb(
Convert.ToByte(rgb.Substring(0, 2), 16),
Convert.ToByte(rgb.Substring(2, 2), 16),
Convert.ToByte(rgb.Substring(4, 2), 16))
};
}
return result;
}
static BorderStyle MapBorderStyleFromExcel(BorderStyleValues value)
{
if (value == BorderStyleValues.Thin)
{
return BorderStyle.Thin;
}
else if (value == BorderStyleValues.Medium)
{
return BorderStyle.Medium;
}
else if (value == BorderStyleValues.Dashed)
{
return BorderStyle.Dashed;
}
else if (value == BorderStyleValues.Dotted)
{
return BorderStyle.Dotted;
}
else if (value == BorderStyleValues.Thick)
{
return BorderStyle.Thick;
}
else if (value == BorderStyleValues.Double)
{
return BorderStyle.Double;
}
else if (value == BorderStyleValues.Hair)
{
return BorderStyle.Hair;
}
else if (value == BorderStyleValues.MediumDashed)
{
return BorderStyle.MediumDashed;
}
else if (value == BorderStyleValues.DashDot)
{
return BorderStyle.DashDot;
}
else if (value == BorderStyleValues.MediumDashDot)
{
return BorderStyle.MediumDashDot;
}
else if (value == BorderStyleValues.DashDotDot)
{
return BorderStyle.DashDotDot;
}
else if (value == BorderStyleValues.MediumDashDotDot)
{
return BorderStyle.MediumDashDotDot;
}
else if (value == BorderStyleValues.SlantDashDot)
{
return BorderStyle.SlantDashDot;
}
else throw new NotSupportedException($"Unsupported border style: {value}");
}
public override bool Equals(object? obj) => obj is BorderSide other && Equals(other);
public bool Equals(BorderSide other) => this == other;
public static bool operator ==(BorderSide left, BorderSide right) =>
left.Style == right.Style && Equals(left.Color, right.Color);
public static bool operator !=(BorderSide left, BorderSide right) => !(left == right);
public override int GetHashCode()
{
unchecked
{
int hash = 17;
hash = hash * 31 + Style.GetHashCode();
hash = hash * 31 + (Color?.GetHashCode() ?? 0);
return hash;
}
}
}
/// Тип линии границы
public enum BorderStyle
{
/// Тонкая линия
Thin,
/// Средняя линия
Medium,
/// Штриховая
Dashed,
/// Точечная
Dotted,
/// Толстая линия
Thick,
/// Двойная линия
Double,
/// Волосяная (очень тонкая)
Hair,
/// Средняя штриховая
MediumDashed,
/// Штрих-пунктирная
DashDot,
/// Средняя штрих-пунктирная
MediumDashDot,
/// Штрих-пунктир-пунктир
DashDotDot,
/// Средняя штрих-пунктир-пунктир
MediumDashDotDot,
/// Наклонная штрих-пунктирная (для диагональных)
SlantDashDot,
}
///
/// Определяет, какие границы диапазона следует применить.
///
public enum BorderTarget
{
/// Все границы (внешние и внутренние) – полная сетка.
All,
/// Только внешние границы диапазона.
Outside,
/// Только внутренние границы (между ячейками).
Inside,
/// Только верхняя граница диапазона.
Top,
/// Только нижняя граница диапазона.
Bottom,
/// Только левая граница диапазона.
Left,
/// Только правая граница диапазона.
Right
}