1 1.1 jmmv // Copyright 2012 Google Inc. 2 1.1 jmmv // All rights reserved. 3 1.1 jmmv // 4 1.1 jmmv // Redistribution and use in source and binary forms, with or without 5 1.1 jmmv // modification, are permitted provided that the following conditions are 6 1.1 jmmv // met: 7 1.1 jmmv // 8 1.1 jmmv // * Redistributions of source code must retain the above copyright 9 1.1 jmmv // notice, this list of conditions and the following disclaimer. 10 1.1 jmmv // * Redistributions in binary form must reproduce the above copyright 11 1.1 jmmv // notice, this list of conditions and the following disclaimer in the 12 1.1 jmmv // documentation and/or other materials provided with the distribution. 13 1.1 jmmv // * Neither the name of Google Inc. nor the names of its contributors 14 1.1 jmmv // may be used to endorse or promote products derived from this software 15 1.1 jmmv // without specific prior written permission. 16 1.1 jmmv // 17 1.1 jmmv // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 1.1 jmmv // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 1.1 jmmv // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 1.1 jmmv // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 1.1 jmmv // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 1.1 jmmv // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 1.1 jmmv // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 1.1 jmmv // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 1.1 jmmv // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 1.1 jmmv // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 1.1 jmmv // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 1.1 jmmv 29 1.1 jmmv #include "utils/text/table.hpp" 30 1.1 jmmv 31 1.1 jmmv #include <algorithm> 32 1.1 jmmv #include <iterator> 33 1.1 jmmv #include <limits> 34 1.1 jmmv #include <sstream> 35 1.1 jmmv 36 1.1 jmmv #include "utils/sanity.hpp" 37 1.1 jmmv #include "utils/text/operations.ipp" 38 1.1 jmmv 39 1.1 jmmv namespace text = utils::text; 40 1.1 jmmv 41 1.1 jmmv 42 1.1 jmmv namespace { 43 1.1 jmmv 44 1.1 jmmv 45 1.1 jmmv /// Applies user overrides to the column widths of a table. 46 1.1 jmmv /// 47 1.1 jmmv /// \param table The table from which to calculate the column widths. 48 1.1 jmmv /// \param user_widths The column widths provided by the user. This vector must 49 1.1 jmmv /// have less or the same number of elements as the columns of the table. 50 1.1 jmmv /// Values of width_auto are ignored; any other explicit values are copied 51 1.1 jmmv /// to the output widths vector, including width_refill. 52 1.1 jmmv /// 53 1.1 jmmv /// \return A vector with the widths of the columns of the input table with any 54 1.1 jmmv /// user overrides applied. 55 1.1 jmmv static text::widths_vector 56 1.1 jmmv override_column_widths(const text::table& table, 57 1.1 jmmv const text::widths_vector& user_widths) 58 1.1 jmmv { 59 1.1 jmmv PRE(user_widths.size() <= table.ncolumns()); 60 1.1 jmmv text::widths_vector widths = table.column_widths(); 61 1.1 jmmv 62 1.1 jmmv // Override the actual width of the columns based on user-specified widths. 63 1.1 jmmv for (text::widths_vector::size_type i = 0; i < user_widths.size(); ++i) { 64 1.1 jmmv const text::widths_vector::value_type& user_width = user_widths[i]; 65 1.1 jmmv if (user_width != text::table_formatter::width_auto) { 66 1.1 jmmv PRE_MSG(user_width == text::table_formatter::width_refill || 67 1.1 jmmv user_width >= widths[i], 68 1.1 jmmv "User-provided column widths must be larger than the " 69 1.1 jmmv "column contents (except for the width_refill column)"); 70 1.1 jmmv widths[i] = user_width; 71 1.1 jmmv } 72 1.1 jmmv } 73 1.1 jmmv 74 1.1 jmmv return widths; 75 1.1 jmmv } 76 1.1 jmmv 77 1.1 jmmv 78 1.1 jmmv /// Locates the refill column, if any. 79 1.1 jmmv /// 80 1.1 jmmv /// \param widths The widths of the columns as returned by 81 1.1 jmmv /// override_column_widths(). Note that one of the columns may or may not 82 1.1 jmmv /// be width_refill, which is the column we are looking for. 83 1.1 jmmv /// 84 1.1 jmmv /// \return The index of the refill column with a width_refill width if any, or 85 1.1 jmmv /// otherwise the index of the last column (which is the default refill column). 86 1.1 jmmv static text::widths_vector::size_type 87 1.1 jmmv find_refill_column(const text::widths_vector& widths) 88 1.1 jmmv { 89 1.1 jmmv text::widths_vector::size_type i = 0; 90 1.1 jmmv for (; i < widths.size(); ++i) { 91 1.1 jmmv if (widths[i] == text::table_formatter::width_refill) 92 1.1 jmmv return i; 93 1.1 jmmv } 94 1.1 jmmv return i - 1; 95 1.1 jmmv } 96 1.1 jmmv 97 1.1 jmmv 98 1.1 jmmv /// Pads the widths of the table to fit within a maximum width. 99 1.1 jmmv /// 100 1.1 jmmv /// On output, a column of the widths vector is truncated to a shorter length 101 1.1 jmmv /// than its current value, if the total width of the table would exceed the 102 1.1 jmmv /// maximum table width. 103 1.1 jmmv /// 104 1.1 jmmv /// \param [in,out] widths The widths of the columns as returned by 105 1.1 jmmv /// override_column_widths(). One of these columns should have a value of 106 1.1 jmmv /// width_refill; if not, a default column is refilled. 107 1.1 jmmv /// \param user_max_width The target width of the table; must not be zero. 108 1.1 jmmv /// \param column_padding The padding between the cells, if any. The target 109 1.1 jmmv /// width should be larger than the padding times the number of columns; if 110 1.1 jmmv /// that is not the case, we attempt a readjustment here. 111 1.1 jmmv static void 112 1.1 jmmv refill_widths(text::widths_vector& widths, 113 1.1 jmmv const text::widths_vector::value_type user_max_width, 114 1.1 jmmv const std::size_t column_padding) 115 1.1 jmmv { 116 1.1 jmmv PRE(user_max_width != 0); 117 1.1 jmmv 118 1.1 jmmv // widths.size() is a proxy for the number of columns of the table. 119 1.1 jmmv const std::size_t total_padding = column_padding * (widths.size() - 1); 120 1.1 jmmv const text::widths_vector::value_type max_width = std::max( 121 1.1 jmmv user_max_width, total_padding) - total_padding; 122 1.1 jmmv 123 1.1 jmmv const text::widths_vector::size_type refill_column = 124 1.1 jmmv find_refill_column(widths); 125 1.1 jmmv INV(refill_column < widths.size()); 126 1.1 jmmv 127 1.1 jmmv text::widths_vector::value_type width = 0; 128 1.1 jmmv for (text::widths_vector::size_type i = 0; i < widths.size(); ++i) { 129 1.1 jmmv if (i != refill_column) 130 1.1 jmmv width += widths[i]; 131 1.1 jmmv } 132 1.1 jmmv widths[refill_column] = max_width - width; 133 1.1 jmmv } 134 1.1 jmmv 135 1.1 jmmv 136 1.1 jmmv /// Pads an input text to a specified width with spaces. 137 1.1 jmmv /// 138 1.1 jmmv /// \param input The text to add padding to (may be empty). 139 1.1 jmmv /// \param length The desired length of the output. 140 1.1 jmmv /// \param is_last Whether the text being processed belongs to the last column 141 1.1 jmmv /// of a row or not. Values in the last column should not be padded to 142 1.1 jmmv /// prevent trailing whitespace on the screen (which affects copy/pasting 143 1.1 jmmv /// for example). 144 1.1 jmmv /// 145 1.1 jmmv /// \return The padded cell. If the input string is longer than the desired 146 1.1 jmmv /// length, the input string is returned verbatim. The padded table won't be 147 1.1 jmmv /// correct, but we don't expect this to be a common case to worry about. 148 1.1 jmmv static std::string 149 1.1 jmmv pad_cell(const std::string& input, const std::size_t length, const bool is_last) 150 1.1 jmmv { 151 1.1 jmmv if (is_last) 152 1.1 jmmv return input; 153 1.1 jmmv else { 154 1.1 jmmv if (input.length() < length) 155 1.1 jmmv return input + std::string(length - input.length(), ' '); 156 1.1 jmmv else 157 1.1 jmmv return input; 158 1.1 jmmv } 159 1.1 jmmv } 160 1.1 jmmv 161 1.1 jmmv 162 1.1 jmmv /// Refills a cell and adds it to the output lines. 163 1.1 jmmv /// 164 1.1 jmmv /// \param row The row containing the cell to be refilled. 165 1.1 jmmv /// \param widths The widths of the row. 166 1.1 jmmv /// \param column The column being refilled. 167 1.1 jmmv /// \param [in,out] textual_rows The output lines as processed so far. This is 168 1.1 jmmv /// updated to accomodate for the contents of the refilled cell, extending 169 1.1 jmmv /// the rows as necessary. 170 1.1 jmmv static void 171 1.1 jmmv refill_cell(const text::table_row& row, const text::widths_vector& widths, 172 1.1 jmmv const text::table_row::size_type column, 173 1.1 jmmv std::vector< text::table_row >& textual_rows) 174 1.1 jmmv { 175 1.1 jmmv const std::vector< std::string > rows = text::refill(row[column], 176 1.1 jmmv widths[column]); 177 1.1 jmmv 178 1.1 jmmv if (textual_rows.size() < rows.size()) 179 1.1 jmmv textual_rows.resize(rows.size(), text::table_row(row.size())); 180 1.1 jmmv 181 1.1 jmmv for (std::vector< std::string >::size_type i = 0; i < rows.size(); ++i) { 182 1.1 jmmv for (text::table_row::size_type j = 0; j < row.size(); ++j) { 183 1.1 jmmv const bool is_last = j == row.size() - 1; 184 1.1 jmmv if (j == column) 185 1.1 jmmv textual_rows[i][j] = pad_cell(rows[i], widths[j], is_last); 186 1.1 jmmv else { 187 1.1 jmmv if (textual_rows[i][j].empty()) 188 1.1 jmmv textual_rows[i][j] = pad_cell("", widths[j], is_last); 189 1.1 jmmv } 190 1.1 jmmv } 191 1.1 jmmv } 192 1.1 jmmv } 193 1.1 jmmv 194 1.1 jmmv 195 1.1 jmmv /// Formats a single table row. 196 1.1 jmmv /// 197 1.1 jmmv /// \param row The row to format. 198 1.1 jmmv /// \param widths The widths of the columns to apply during formatting. Cells 199 1.1 jmmv /// wider than the specified width are refilled to attempt to fit in the 200 1.1 jmmv /// cell. Cells narrower than the width are right-padded with spaces. 201 1.1 jmmv /// \param separator The column separator to use. 202 1.1 jmmv /// 203 1.1 jmmv /// \return The textual lines that contain the formatted row. 204 1.1 jmmv static std::vector< std::string > 205 1.1 jmmv format_row(const text::table_row& row, const text::widths_vector& widths, 206 1.1 jmmv const std::string& separator) 207 1.1 jmmv { 208 1.1 jmmv PRE(row.size() == widths.size()); 209 1.1 jmmv 210 1.1 jmmv std::vector< text::table_row > textual_rows(1, text::table_row(row.size())); 211 1.1 jmmv 212 1.1 jmmv for (text::table_row::size_type column = 0; column < row.size(); ++column) { 213 1.1 jmmv if (widths[column] > row[column].length()) 214 1.1 jmmv textual_rows[0][column] = pad_cell(row[column], widths[column], 215 1.1 jmmv column == row.size() - 1); 216 1.1 jmmv else 217 1.1 jmmv refill_cell(row, widths, column, textual_rows); 218 1.1 jmmv } 219 1.1 jmmv 220 1.1 jmmv std::vector< std::string > lines; 221 1.1 jmmv for (std::vector< text::table_row >::const_iterator 222 1.1 jmmv iter = textual_rows.begin(); iter != textual_rows.end(); ++iter) { 223 1.1 jmmv lines.push_back(text::join(*iter, separator)); 224 1.1 jmmv } 225 1.1 jmmv return lines; 226 1.1 jmmv } 227 1.1 jmmv 228 1.1 jmmv 229 1.1 jmmv } // anonymous namespace 230 1.1 jmmv 231 1.1 jmmv 232 1.1 jmmv /// Constructs a new table. 233 1.1 jmmv /// 234 1.1 jmmv /// \param ncolumns_ The number of columns that the table will have. 235 1.1 jmmv text::table::table(const table_row::size_type ncolumns_) 236 1.1 jmmv { 237 1.1 jmmv _column_widths.resize(ncolumns_, 0); 238 1.1 jmmv } 239 1.1 jmmv 240 1.1 jmmv 241 1.1 jmmv /// Gets the number of columns in the table. 242 1.1 jmmv /// 243 1.1 jmmv /// \return The number of columns in the table. This value remains constant 244 1.1 jmmv /// during the existence of the table. 245 1.1 jmmv text::widths_vector::size_type 246 1.1 jmmv text::table::ncolumns(void) const 247 1.1 jmmv { 248 1.1 jmmv return _column_widths.size(); 249 1.1 jmmv } 250 1.1 jmmv 251 1.1 jmmv 252 1.1 jmmv /// Gets the width of a column. 253 1.1 jmmv /// 254 1.1 jmmv /// The returned value is not valid if add_row() is called again, as the column 255 1.1 jmmv /// may have grown in width. 256 1.1 jmmv /// 257 1.1 jmmv /// \param column The index of the column of which to get the width. Must be 258 1.1 jmmv /// less than the total number of columns. 259 1.1 jmmv /// 260 1.1 jmmv /// \return The width of a column. 261 1.1 jmmv text::widths_vector::value_type 262 1.1 jmmv text::table::column_width(const widths_vector::size_type column) const 263 1.1 jmmv { 264 1.1 jmmv PRE(column < _column_widths.size()); 265 1.1 jmmv return _column_widths[column]; 266 1.1 jmmv } 267 1.1 jmmv 268 1.1 jmmv 269 1.1 jmmv /// Gets the widths of all columns. 270 1.1 jmmv /// 271 1.1 jmmv /// The returned value is not valid if add_row() is called again, as the columns 272 1.1 jmmv /// may have grown in width. 273 1.1 jmmv /// 274 1.1 jmmv /// \return A vector with the width of all columns. 275 1.1 jmmv const text::widths_vector& 276 1.1 jmmv text::table::column_widths(void) const 277 1.1 jmmv { 278 1.1 jmmv return _column_widths; 279 1.1 jmmv } 280 1.1 jmmv 281 1.1 jmmv 282 1.1 jmmv /// Checks whether the table is empty or not. 283 1.1 jmmv /// 284 1.1 jmmv /// \return True if the table is empty; false otherwise. 285 1.1 jmmv bool 286 1.1 jmmv text::table::empty(void) const 287 1.1 jmmv { 288 1.1 jmmv return _rows.empty(); 289 1.1 jmmv } 290 1.1 jmmv 291 1.1 jmmv 292 1.1 jmmv /// Adds a row to the table. 293 1.1 jmmv /// 294 1.1 jmmv /// \param row The row to be added. This row must have the same amount of 295 1.1 jmmv /// columns as defined during the construction of the table. 296 1.1 jmmv void 297 1.1 jmmv text::table::add_row(const table_row& row) 298 1.1 jmmv { 299 1.1 jmmv PRE(row.size() == _column_widths.size()); 300 1.1 jmmv _rows.push_back(row); 301 1.1 jmmv 302 1.1 jmmv for (table_row::size_type i = 0; i < row.size(); ++i) 303 1.1 jmmv if (_column_widths[i] < row[i].length()) 304 1.1 jmmv _column_widths[i] = row[i].length(); 305 1.1 jmmv } 306 1.1 jmmv 307 1.1 jmmv 308 1.1 jmmv /// Gets an iterator pointing to the beginning of the rows of the table. 309 1.1 jmmv /// 310 1.1 jmmv /// \return An iterator on the rows. 311 1.1 jmmv text::table::const_iterator 312 1.1 jmmv text::table::begin(void) const 313 1.1 jmmv { 314 1.1 jmmv return _rows.begin(); 315 1.1 jmmv } 316 1.1 jmmv 317 1.1 jmmv 318 1.1 jmmv /// Gets an iterator pointing to the end of the rows of the table. 319 1.1 jmmv /// 320 1.1 jmmv /// \return An iterator on the rows. 321 1.1 jmmv text::table::const_iterator 322 1.1 jmmv text::table::end(void) const 323 1.1 jmmv { 324 1.1 jmmv return _rows.end(); 325 1.1 jmmv } 326 1.1 jmmv 327 1.1 jmmv 328 1.1 jmmv /// Column width to denote that the column has to fit all of its cells. 329 1.1 jmmv const std::size_t text::table_formatter::width_auto = 0; 330 1.1 jmmv 331 1.1 jmmv 332 1.1 jmmv /// Column width to denote that the column can be refilled to fit the table. 333 1.1 jmmv const std::size_t text::table_formatter::width_refill = 334 1.1 jmmv std::numeric_limits< std::size_t >::max(); 335 1.1 jmmv 336 1.1 jmmv 337 1.1 jmmv /// Constructs a new table formatter. 338 1.1 jmmv text::table_formatter::table_formatter(void) : 339 1.1 jmmv _separator(""), 340 1.1 jmmv _table_width(0) 341 1.1 jmmv { 342 1.1 jmmv } 343 1.1 jmmv 344 1.1 jmmv 345 1.1 jmmv /// Sets the width of a column. 346 1.1 jmmv /// 347 1.1 jmmv /// All columns except one must have a width that is, at least, as wide as the 348 1.1 jmmv /// widest cell in the column. One of the columns can have a width of 349 1.1 jmmv /// width_refill, which indicates that the column will be refilled if the table 350 1.1 jmmv /// does not fit in its maximum width. 351 1.1 jmmv /// 352 1.1 jmmv /// \param column The index of the column to set the width for. 353 1.1 jmmv /// \param width The width to set the column to. 354 1.1 jmmv /// 355 1.1 jmmv /// \return A reference to this formatter to allow using the builder pattern. 356 1.1 jmmv text::table_formatter& 357 1.1 jmmv text::table_formatter::set_column_width(const table_row::size_type column, 358 1.1 jmmv const std::size_t width) 359 1.1 jmmv { 360 1.1 jmmv #if !defined(NDEBUG) 361 1.1 jmmv if (width == width_refill) { 362 1.1 jmmv for (widths_vector::size_type i = 0; i < _column_widths.size(); i++) { 363 1.1 jmmv if (i != column) 364 1.1 jmmv PRE_MSG(_column_widths[i] != width_refill, 365 1.1 jmmv "Only one column width can be set to width_refill"); 366 1.1 jmmv } 367 1.1 jmmv } 368 1.1 jmmv #endif 369 1.1 jmmv 370 1.1 jmmv if (_column_widths.size() < column + 1) 371 1.1 jmmv _column_widths.resize(column + 1, width_auto); 372 1.1 jmmv _column_widths[column] = width; 373 1.1 jmmv return *this; 374 1.1 jmmv } 375 1.1 jmmv 376 1.1 jmmv 377 1.1 jmmv /// Sets the separator to use between the cells. 378 1.1 jmmv /// 379 1.1 jmmv /// \param separator The separator to use. 380 1.1 jmmv /// 381 1.1 jmmv /// \return A reference to this formatter to allow using the builder pattern. 382 1.1 jmmv text::table_formatter& 383 1.1 jmmv text::table_formatter::set_separator(const char* separator) 384 1.1 jmmv { 385 1.1 jmmv _separator = separator; 386 1.1 jmmv return *this; 387 1.1 jmmv } 388 1.1 jmmv 389 1.1 jmmv 390 1.1 jmmv /// Sets the maximum width of the table. 391 1.1 jmmv /// 392 1.1 jmmv /// \param table_width The maximum width of the table; cannot be zero. 393 1.1 jmmv /// 394 1.1 jmmv /// \return A reference to this formatter to allow using the builder pattern. 395 1.1 jmmv text::table_formatter& 396 1.1 jmmv text::table_formatter::set_table_width(const std::size_t table_width) 397 1.1 jmmv { 398 1.1 jmmv PRE(table_width > 0); 399 1.1 jmmv _table_width = table_width; 400 1.1 jmmv return *this; 401 1.1 jmmv } 402 1.1 jmmv 403 1.1 jmmv 404 1.1 jmmv /// Formats a table into a collection of textual lines. 405 1.1 jmmv /// 406 1.1 jmmv /// \param t Table to format. 407 1.1 jmmv /// 408 1.1 jmmv /// \return A collection of textual lines. 409 1.1 jmmv std::vector< std::string > 410 1.1 jmmv text::table_formatter::format(const table& t) const 411 1.1 jmmv { 412 1.1 jmmv std::vector< std::string > lines; 413 1.1 jmmv 414 1.1 jmmv if (!t.empty()) { 415 1.1 jmmv widths_vector widths = override_column_widths(t, _column_widths); 416 1.1 jmmv if (_table_width != 0) 417 1.1 jmmv refill_widths(widths, _table_width, _separator.length()); 418 1.1 jmmv 419 1.1 jmmv for (table::const_iterator iter = t.begin(); iter != t.end(); ++iter) { 420 1.1 jmmv const std::vector< std::string > sublines = 421 1.1 jmmv format_row(*iter, widths, _separator); 422 1.1 jmmv std::copy(sublines.begin(), sublines.end(), 423 1.1 jmmv std::back_inserter(lines)); 424 1.1 jmmv } 425 1.1 jmmv } 426 1.1 jmmv 427 1.1 jmmv return lines; 428 1.1 jmmv } 429