namespace QWERTYkez.ExcelProcessor; /// /// Определяет границы ячейки: верхнюю, нижнюю, левую, правую и диагональные. /// Каждая граница может иметь стиль и цвет. /// public readonly struct CellBorder : IEquatable { public static CellBorder BottomThin { get; } = new() { BottomBorder = BorderSide.BlackThin }; public static CellBorder TopThin { get; } = new() { TopBorder = BorderSide.BlackThin }; public static CellBorder LeftThin { get; } = new() { LeftBorder = BorderSide.BlackThin }; public static CellBorder RightThin { get; } = new() { RightBorder = BorderSide.BlackThin }; public static CellBorder AllThin { get; } = new() { BottomBorder = BorderSide.BlackThin, TopBorder = BorderSide.BlackThin, LeftBorder = BorderSide.BlackThin, RightBorder = BorderSide.BlackThin }; public static CellBorder BottomMedium { get; } = new() { BottomBorder = BorderSide.BlackMedium }; public static CellBorder TopMedium { get; } = new() { TopBorder = BorderSide.BlackMedium }; public static CellBorder LeftMedium { get; } = new() { LeftBorder = BorderSide.BlackMedium }; public static CellBorder RightMedium { get; } = new() { RightBorder = BorderSide.BlackMedium }; public static CellBorder AllMedium { get; } = new() { BottomBorder = BorderSide.BlackMedium, TopBorder = BorderSide.BlackMedium, LeftBorder = BorderSide.BlackMedium, RightBorder = BorderSide.BlackMedium }; public static CellBorder BottomThick { get; } = new() { BottomBorder = BorderSide.BlackThick }; public static CellBorder TopThick { get; } = new() { TopBorder = BorderSide.BlackThick }; public static CellBorder LeftThick { get; } = new() { LeftBorder = BorderSide.BlackThick }; public static CellBorder RightThick { get; } = new() { RightBorder = BorderSide.BlackThick }; public static CellBorder AllThick { get; } = new() { BottomBorder = BorderSide.BlackThick, TopBorder = BorderSide.BlackThick, LeftBorder = BorderSide.BlackThick, RightBorder = BorderSide.BlackThick }; /// Верхняя граница. 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 }