using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ZeroLevel.Services.PlainTextTables { public class TextTableRender { public static string Render(TextTableData data, TextTableRenderOptions options) { try { var columns_width = data. Columns. Select(c => c.Width). ToArray(); var ow = (options.PaddingLeft > 0 ? options.PaddingLeft : 0) + (options.PaddingRight > 0 ? options.PaddingRight : 0); for (int i = 0; i < columns_width.Length; i++) { columns_width[i] += ow; } // Update column widths as needed if (options.MaxCellWidth > 0) { for (int i = 0; i < columns_width.Length; i++) { if (columns_width[i] > options.MaxCellWidth) { columns_width[i] = options.MaxCellWidth; } } } else if (options.MaxTableWidth > 0) { int index = 0; while (GetTableWidth(options, columns_width) > options.MaxTableWidth) { for (int i = 0; i < columns_width.Length; i++) { if (columns_width[i] > columns_width[index]) index = i; } columns_width[index]--; } } var table_width = GetTableWidth(options, columns_width); var table = new StringBuilder(); // Table drawing var rows = data.Rows.ToArray(); for (int i = 0; i < rows.Length; i++) { DrawRowSeparator(table, options, table_width, i, i == 0, false, columns_width); var row_render = RenderRow(rows[i], options, columns_width); foreach (var line in row_render) { int c = 0; for (; c < columns_width.Length; c++) { DrawColumnSeparator(table, options, c); table.Append(line[c]); } DrawColumnSeparator(table, options, c); table.AppendLine(); } } DrawRowSeparator(table, options, table_width, data.Rows.Count(), false, true, columns_width); return table.ToString(); } catch (Exception ex) { Log.SystemError(ex, "Failed to convert table"); return string.Empty; } } /// /// Render row view /// private static List RenderRow(TextTableData.TextTableRow row, TextTableRenderOptions options, int[] columns_width) { var result = new List(); // Adding blank lines if there is padding on top if (options.PaddingTop > 0) { for (int i = 0; i < options.PaddingTop; i++) { var empty = new string[columns_width.Length]; for (int c = 0; c < columns_width.Length; c++) { empty[c] = new string(' ', columns_width[c]); } result.Add(empty); } } // Separation of cell values into parts depending on the width of the column var cells = new List(); for (int i = 0; i < columns_width.Length; i++) { cells.Add(Split(row.Cells[i].Text, columns_width[i], options.PaddingLeft, options.PaddingRight).ToArray()); } // Determining the maximum number of rows per cell (row height of the table) var max = cells.Max(c => c.Length); for (int i = 0; i < max; i++) { var line = new string[columns_width.Length]; for (int c = 0; c < columns_width.Length; c++) { var text = (cells[c].Length > i) ? cells[c][i] : string.Empty; if (text.Length < columns_width[c]) { text = text + new string(' ', columns_width[c] - text.Length); } line[c] = text; } result.Add(line); } // Adding blank lines if there is padding below if (options.PaddingBottom > 0) { for (int i = 0; i < options.PaddingBottom; i++) { var empty = new string[columns_width.Length]; for (int c = 0; c < columns_width.Length; c++) { empty[c] = new string(' ', columns_width[c]); } result.Add(empty); } } return result; } /// /// Splitting a text string into substrings of the specified length /// private static IEnumerable Split(string str, int chunkSize, int leftPad, int rightPad) { if (str == null) return new string[1] { string.Empty }; while ((chunkSize - (leftPad + rightPad)) < 5 && (leftPad > 0 || rightPad > 0)) { if (leftPad > 0) leftPad--; if (rightPad > 0) rightPad--; } var size = chunkSize - leftPad - rightPad; var add = str.Length % size > 0 ? 1 : 0; var count = (int)(str.Length / size) + add; var result = new string[count]; for (int i = 0; i < count; i++) { var start = i * size; var diff = str.Length - i * size; var length = size; if (diff < length) length = diff; result[i] = (start < str.Length) ? str.Substring(start, length) : string.Empty; if (leftPad > 0 && result[i].Length < chunkSize) { result[i] = new string(' ', leftPad) + result[i]; } if (rightPad > 0 && result[i].Length < chunkSize) { result[i] = result[i] + new string(' ', rightPad); } } return result; } /// /// Calculate the actual width of the table for the specified style /// private static int GetTableWidth(TextTableRenderOptions options, int[] columns_width) { int width = columns_width.Sum() + // width of text areas columns_width.Length - 1; // cell boundaries switch (options.Style) { case TextTableStyle.Columns: case TextTableStyle.Simple: case TextTableStyle.Borders: case TextTableStyle.DoubleBorders: width += 2; // outer borders break; } return width; } /// /// Rendering column separator /// private static void DrawColumnSeparator(StringBuilder sb, TextTableRenderOptions options, int column_index) { switch (options.Style) { case TextTableStyle.NoBorders: case TextTableStyle.HeaderLine: case TextTableStyle.DoubleHeaderLine: sb.Append(' '); break; case TextTableStyle.Columns: case TextTableStyle.Simple: case TextTableStyle.Borders: case TextTableStyle.DoubleBorders: sb.Append(options.VerticalLine); break; case TextTableStyle.DoubleHeaderAndFirstColumn: case TextTableStyle.HeaderAndFirstColumn: if (column_index == 1) { sb.Append(options.VerticalLine); } else { sb.Append(' '); } break; } } /// /// Row separator rendering /// /// /// Line index, meaning the index of the next line, those 0 - before the first line is drawn private static void DrawRowSeparator(StringBuilder sb, TextTableRenderOptions options, int width, int row_index, bool isFirst, bool isLast, int[] columns_width) { var sym = options.HorizontalLine; switch (options.Style) { case TextTableStyle.Columns: case TextTableStyle.Simple: case TextTableStyle.Borders: case TextTableStyle.DoubleBorders: if (isFirst) { sb.Append(options.LeftTopCorner); for (int i = 0; i < columns_width.Length; i++) { sb.Append(options.HorizontalLine, columns_width[i]); if (i != columns_width.Length - 1) { sb.Append(options.HorizontalToDownLine); } } sb.Append(options.RightTopCorner); } else if (isLast) { sb.Append(options.LeftBottomCorner); for (int i = 0; i < columns_width.Length; i++) { sb.Append(options.HorizontalLine, columns_width[i]); if (i != columns_width.Length - 1) { sb.Append(options.HorizontalToUpLine); } } sb.Append(options.RightBottomCorner); } else { sb.Append(options.VerticalToRightLine); for (int i = 0; i < columns_width.Length; i++) { if (options.Style == TextTableStyle.Columns) sb.Append(' ', columns_width[i]); else sb.Append(options.HorizontalLine, columns_width[i]); if (i != columns_width.Length - 1) { sb.Append(options.CrossLines); } } sb.Append(options.VerticalToLeftLine); } break; case TextTableStyle.HeaderLine: case TextTableStyle.DoubleHeaderLine: if (row_index == 1) { sb.Append(sym, width); } break; case TextTableStyle.HeaderAndFirstColumn: case TextTableStyle.DoubleHeaderAndFirstColumn: if (row_index == 1) { sb.Append(sym, columns_width[0] + 1); sb.Append(options.CrossLines); sb.Append(sym, width - columns_width[0] - 1); } break; } sb.AppendLine(); } } }