1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5 using System.Threading.Tasks;
6
7 namespace datatable
8 {
9 public class ConsoleTable
10 {
11 /// <summary>
12 /// This will hold the header of the table.
13 /// </summary>
14 private string[] header;
15
16 /// <summary>
17 /// This will hold the rows (lines) in the table, not including the
18 /// header. I'm using a List of lists because it's easier to deal with...
19 /// </summary>
20 private List<List<string>> rows;
21
22 /// <summary>
23 /// This is the default element (character/string) that will be put
24 /// in the table when user adds invalid data, example:
25 /// ConsoleTable ct = new ConsoleTable();
26 /// ct.AddRow(new List<string> { null, "bla", "bla" });
27 /// That null will be replaced with "DefaultElement", also, empty
28 /// strings will be replaced with this value.
29 /// </summary>
30 private const string DefaultElement = "X";
31
32 public enum AlignText
33 {
34 ALIGN_RIGHT,
35 ALIGN_LEFT,
36 }
37
38 public ConsoleTable()
39 {
40 header = null;
41 rows = new List<List<string>>();
42 TextAlignment = AlignText.ALIGN_LEFT;
43 }
44
45 /// <summary>
46 /// Set text alignment in table cells, either RIGHT or LEFT.
47 /// </summary>
48 public AlignText TextAlignment
49 {
50 get;
51 set;
52 }
53
54 public void SetHeaders(string[] h)
55 {
56 header = h;
57 }
58
59 public void AddRow(List<string> row)
60 {
61 rows.Add(row);
62 }
63
64 private void AppendLine(StringBuilder hsb, int length)
65 {
66 // " " length is 1
67 // "\r\n" length is 2
68 // +1 length because I want the output to be prettier
69 // Hence the length - 4 ...
70 hsb.Append(" ");
71 hsb.Append(new string('-', length - 4));
72 hsb.Append("\r\n");
73 }
74
75 /// <summary>
76 /// This function returns the maximum possible length of an
77 /// individual row (line). Of course that if we use table header,
78 /// the maximum length of an individual row should equal the
79 /// length of the header.
80 /// </summary>
81 private int GetMaxRowLength()
82 {
83 if (header != null)
84 return header.Length;
85 else
86 {
87 int maxlen = rows[0].Count;
88 for (int i = 1; i < rows.Count; i++)
89 if (rows[i].Count > maxlen)
90 maxlen = rows[i].Count;
91
92 return maxlen;
93 }
94 }
95
96 private void PutDefaultElementAndRemoveExtra()
97 {
98 int maxlen = GetMaxRowLength();
99
100 for (int i = 0; i < rows.Count; i++)
101 {
102 // If we find a line that is smaller than the biggest line,
103 // we'll add DefaultElement at the end of that line. In the end
104 // the line will be as big as the biggest line.
105 if (rows[i].Count < maxlen)
106 {
107 int loops = maxlen - rows[i].Count;
108 for (int k = 0; k < loops; k++)
109 rows[i].Add(DefaultElement);
110 }
111 else if (rows[i].Count > maxlen)
112 {
113 // This will apply only when header != null, and we try to
114 // add a line bigger than the header line. Remove the elements
115 // of the line, from right to left, until the line is equal
116 // with the header line.
117 rows[i].RemoveRange(maxlen, rows[i].Count - maxlen);
118 }
119
120 // Find bad data, loop through all table elements.
121 for (int j = 0; j < rows[i].Count; j++)
122 {
123 if (rows[i][j] == null)
124 rows[i][j] = DefaultElement;
125 else if (rows[i][j] == "")
126 rows[i][j] = DefaultElement;
127 }
128 }
129 }
130
131 /// <summary>
132 /// This function will return an array of integers, an element at
133 /// position 'i' will return the maximum length from column 'i'
134 /// of the table (if we look at the table as a matrix).
135 /// </summary>
136 private int[] GetWidths()
137 {
138 int[] widths = null;
139 if (header != null)
140 {
141 // Initially we assume that the maximum length from column 'i'
142 // is exactly the length of the header from column 'i'.
143 widths = new int[header.Length];
144 for (int i = 0; i < header.Length; i++)
145 widths[i] = header[i].ToString().Length;
146 }
147 else
148 {
149 int count = GetMaxRowLength();
150 widths = new int[count];
151 for (int i = 0; i < count; i++)
152 widths[i] = -1;
153 }
154
155 foreach (List<string> s in rows)
156 {
157 for (int i = 0; i < s.Count; i++)
158 {
159 s[i] = s[i].Trim();
160 if (s[i].Length > widths[i])
161 widths[i] = s[i].Length;
162 }
163 }
164
165 return widths;
166 }
167
168 /// <summary>
169 /// Returns a valid format that is to be passed to AppendFormat
170 /// member function of StringBuilder.
171 /// General form: "|{i, +/-widths[i]}|", where 0 <= i <= widths.Length - 1
172 /// and widths[i] represents the maximum width from column 'i'.
173 /// </summary>
174 /// <param name="widths">The array of widths presented above.</param>
175 private string BuildRowFormat(int[] widths)
176 {
177 string rowFormat = String.Empty;
178 for (int i = 0; i < widths.Length; i++)
179 {
180 if (TextAlignment == AlignText.ALIGN_LEFT)
181 rowFormat += "| {" + i.ToString() + ",-" + (widths[i]) + "} ";
182 else
183 rowFormat += "| {" + i.ToString() + "," + (widths[i]) + "} ";
184 }
185
186 rowFormat = rowFormat.Insert(rowFormat.Length, "|\r\n");
187 return rowFormat;
188 }
189
190 /// <summary>
191 /// Prints the table, main function.
192 /// </summary>
193 public void PrintTable()
194 {
195 if (rows.Count == 0)
196 {
197 Console.WriteLine("Can't create a table without any rows.");
198 return;
199 }
200 PutDefaultElementAndRemoveExtra();
201
202 int[] widths = GetWidths();
203 string rowFormat = BuildRowFormat(widths);
204
205 // I'm using a temporary string builder to find the total width
206 // of the table, and increase BufferWidth of Console if necessary.
207 StringBuilder toFindLen = new StringBuilder();
208 toFindLen.AppendFormat(rowFormat, (header == null ? rows[0].ToArray() : header));
209 int length = toFindLen.Length;
210 if (Console.BufferWidth < length)
211 Console.BufferWidth = length;
212
213 // Print the first row, or header (if it exist), you can see that AppendLine
214 // is called before/after every AppendFormat.
215 StringBuilder hsb = new StringBuilder();
216 AppendLine(hsb, length);
217 hsb.AppendFormat(rowFormat, (header == null ? rows[0].ToArray() : header));
218 AppendLine(hsb, length);
219
220 // If header does't exist, we start from 1 because the first row
221 // was already printed above.
222 int idx = 0;
223 if (header == null)
224 idx = 1;
225 for (int i = idx; i < rows.Count; i++)
226 {
227 hsb.AppendFormat(rowFormat, rows[i].ToArray());
228 AppendLine(hsb, length);
229 }
230
231 Console.WriteLine(hsb.ToString());
232 }
233
234
235 static void Main(string[] args)
236 {
237 // Some test table, with header, and 3 lines by 3 columns.
238 ConsoleTable ct = new ConsoleTable();
239 ct.TextAlignment = ConsoleTable.AlignText.ALIGN_RIGHT;
240 ct.SetHeaders(new string[] { "ID", "Name", "City" });
241 ct.AddRow(new List<string> { "1", "John", "New York" });
242 ct.AddRow(new List<string> { "2", "Mark", "Washington" });
243 ct.AddRow(new List<string> { "3", "Alice", "Chicago" });
244 ct.PrintTable();
245 }
246 }
247 }