Program Listing for File Table.cc¶
↰ Return to documentation for file (Table.cc)
/*--------------------------------------------------------------------------*\
| |
| Copyright (C) 2021 |
| |
| , __ , __ |
| /|/ \ /|/ \ |
| | __/ _ ,_ | __/ _ ,_ |
| | \|/ / | | | | \|/ / | | | |
| |(__/|__/ |_/ \_/|/|(__/|__/ |_/ \_/|/ |
| /| /| |
| \| \| |
| |
| Enrico Bertolazzi |
| Dipartimento di Ingegneria Industriale |
| Universita` degli Studi di Trento |
| Via Sommarive 9, I-38123 Povo, Trento, Italy |
| email: enrico.bertolazzi@unitn.it |
| |
\*--------------------------------------------------------------------------*/
/*\
Based on terminal-table:
https://github.com/Bornageek/terminal-table
Copyright 2015 Andreas Wilhelm
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
\*/
#ifndef DOXYGEN_SHOULD_SKIP_THIS
#include "Utils.hh"
#include <string>
#include <iostream>
#include <iomanip>
#include <sstream>
#include <cctype>
#include <algorithm>
#include <functional>
#include <stdexcept>
namespace Utils {
namespace Table {
using std::istringstream;
using std::stringstream;
using std::count;
using std::getline;
using std::find_if;
using std::isspace;
using std::setw;
using std::left;
using std::right;
using std::setfill;
using std::bind;
using std::out_of_range;
using std::to_string;
using std::transform;
Cell::Cell(
Table* table,
string const & val,
integer colSpan
)
: m_Table(table)
, m_Value(val)
, m_ColSpan(colSpan)
{
m_Width = integer(val.length());
}
integer
Cell::width( integer col ) const {
integer padding = (m_ColSpan - 1) * m_Table->cellSpacing();
integer innerWidth = 0;
for( integer i = 0; i < m_ColSpan; ++i )
innerWidth += m_Table->columnWidth(col + i);
return innerWidth + padding;
}
integer
Cell::height() const {
return integer( count( m_Value.begin(), m_Value.end(), '\n') + 1);
}
integer
Cell::maxLineWidth() const {
integer maxlen = 0;
string line;
istringstream stream( m_Value );
while( getline(stream, line) ) {
integer len = integer( line.length() );
if ( len > maxlen ) maxlen = len;
}
return maxlen;
}
string
Cell::line( integer idx ) const {
if ( idx < this->height() ) {
istringstream stream(m_Value);
string line;
for( integer i = 0; i <= idx; ++i) getline(stream, line);
this->trimLine(line);
return line;
}
return "";
}
void
Cell::trimLine( string & line ) const {
auto fun = [] ( char c ) -> bool { return isspace(int(c)) == 0; };
line.erase( line.begin(), find_if( line.begin(), line.end(), fun ) );
line.erase( find_if( line.rbegin(), line.rend(), fun ).base(), line.end() );
}
string
Cell::render( integer line, integer col ) const {
stringstream ss;
integer width = this->width(col);
integer pL = m_Table->style().paddingLeft();
integer pR = m_Table->style().paddingRight();
switch( m_Align ) {
case Alignment::LEFT:
ss << string( pL, ' ' )
<< setw(width) << left << setfill(' ') << this->line(line)
<< string( pR, ' ' );
break;
case Alignment::RIGHT:
ss << string( pL, ' ' )
<< setw(width) << right << setfill(' ') << this->line(line)
<< string( pR, ' ' );
break;
case Alignment::CENTER:
{
string val = this->line(line);
integer innerWidth = width + m_Table->cellPadding();
integer spaceLeft = (innerWidth-integer(val.length()))/2;
ss << string(spaceLeft, ' ')
<< setw(innerWidth - spaceLeft) << left << setfill(' ') << this->line(line);
}
break;
}
return ss.str();
}
Row::Row( Table * table, vecstr const & cells )
: m_Table(table) {
for_each(
cells.begin(), cells.end(),
bind(
static_cast<void(Row::*)(string const &)>(&Row::cell),
this,
std::placeholders::_1
)
);
}
void
Row::cells( vecstr const & cells ) {
for_each(
cells.begin(), cells.end(),
bind(
static_cast<void(Row::*)(string const &)>(&Row::cell),
this,
std::placeholders::_1
)
);
}
integer
Row::cellWidth( integer idx ) const {
if ( idx < this->numCells() ) return m_Cells[idx].maxLineWidth();
return 0;
}
void
Row::cellColSpan( integer idx, integer span ) {
if ( span > 0 && idx < this->numCells() ) m_Cells[idx].colSpan(span);
}
void
Row::cell( string const & value ) {
m_Cells.push_back( Cell( m_Table, value ) );
}
integer
Row::height() const {
integer maxlen = 1;
for_each(
m_Cells.begin(), m_Cells.end(),
[&maxlen]( Cell const & cell ) -> void {
if ( cell.height() > maxlen ) maxlen = cell.height();
}
);
return maxlen;
}
string
Row::render() const {
integer numColumns = m_Table->numColumns();
integer paddingLR = m_Table->style().paddingLeft()+
m_Table->style().paddingRight();
integer numLines = this->height();
stringstream ss;
Style style = m_Table->style();
integer nc = integer(m_Cells.size());
for ( integer l = 0; l < numLines; ++l ) {
ss << style.borderLeft();
for( integer c = 0; c < numColumns; ++c ) {
if ( c < nc ) {
Cell const & C = m_Cells[c];
ss << C.render(l, c);
if ( C.colSpan() > 1 ) c += C.colSpan() - 1;
} else {
ss << string(m_Table->columnWidth(c)+paddingLR,' ');
}
if ( c < numColumns-1 ) ss << style.borderMiddle();
}
ss << style.borderRight() << '\n';
}
return ss.str();
}
void
Table::alignColumn( integer n, Alignment align ) {
if ( n > this->numColumns() )
throw out_of_range(
"Table error: The table just has " + to_string(this->numColumns()) + " columns."
);
for_each(
m_Rows.begin(), m_Rows.end(),
[n, align]( Row & row ) -> void {
if ( n < row.numCells() ) row[n].alignment(align);
}
);
}
void
Table::addRow( vecstr const & row ) {
m_Rows.push_back( Row(this, row) );
}
integer
Table::cellSpacing() const {
return this->cellPadding() + 1;
}
integer
Table::cellPadding() const {
return m_Style.paddingLeft() + m_Style.paddingRight();
}
typename Table::vecCell
Table::column( integer n ) const {
if ( n > this->numColumns() )
throw out_of_range(
"Table error: The table just has " + to_string(this->numColumns()) + " columns."
);
vecCell column(m_Rows.size());
transform(
m_Rows.begin(), m_Rows.end(), column.begin(),
[n]( Row const & row ) -> Cell { return row[n]; }
);
return column;
}
integer
Table::columnWidth( integer n ) const {
if ( n > this->numColumns() )
throw out_of_range(
"Table error: The table just has " + to_string(this->numColumns()) + " columns."
);
integer maxlen = 0;
auto fun = [&maxlen, n]( Row const & row ) -> void {
if ( n < row.numCells() ) {
integer cw = row.cellWidth(n);
if ( cw > maxlen ) maxlen = cw;
}
};
fun( m_Headings );
for_each( m_Rows.begin(), m_Rows.end(), fun );
return maxlen;
}
integer
Table::numColumns() const {
integer maxlen = m_Headings.numCells();
auto fun = [&maxlen]( Row const & row ) -> void {
integer nc = row.numCells();
if ( nc > maxlen ) maxlen = nc;
};
for_each( m_Rows.begin(), m_Rows.end(), fun );
return maxlen;
}
void
Table::headings( vecstr const & headings ) {
m_Headings = Row(this, headings);
}
Row &
Table::row( integer n ) {
if ( n >= integer(m_Rows.size()) )
throw out_of_range(
"Table error: The table just has " + to_string(m_Rows.size()) + " rows."
);
return m_Rows[n];
}
Row const &
Table::row( integer n ) const {
if ( n >= integer(m_Rows.size()) )
throw out_of_range(
"Table error: The table just has " + to_string(m_Rows.size()) + " rows."
);
return m_Rows[n];
}
void
Table::rows( vecvecstr const & rows ) {
m_Rows = vecRow();
for_each(
rows.begin(), rows.end(),
[this]( vecstr const & row ) {
m_Rows.push_back( Row(this, row) );
}
);
}
string
Table::renderSeparator(
char left,
char mid,
char right,
char sep
) const {
stringstream ss;
ss << left;
integer padding_LR = m_Style.paddingLeft() + m_Style.paddingRight();
integer nc = this->numColumns();
for ( integer i = 0; i < nc; ++i ) {
integer width = this->columnWidth(i) + padding_LR;
for ( integer j = 0 ; j < width; ++j ) ss << sep;
if ( i+1 < nc ) ss << mid;
else ss << right;
}
ss << '\n';
return ss.str();
}
string
Table::render() const {
stringstream ss;
string sep = this->renderSeparator(
m_Style.borderLeftMid(),
m_Style.borderMidMid(),
m_Style.borderRightMid(),
m_Style.borderMid()
);
if ( m_Title.length() > 0 ) {
integer innerWidth = (this->numColumns() - 1) * this->cellSpacing() + this->cellPadding();
for ( integer c = 0; c < this->numColumns(); ++c )
innerWidth += this->columnWidth(c);
integer spaceLeft = (innerWidth - integer(m_Title.length())) / 2;
ss << m_Style.borderTopLeft()
<< string(innerWidth, m_Style.borderTop())
<< m_Style.borderTopRight()
<< '\n'
<< m_Style.borderLeft()
<< string(spaceLeft, ' ')
<< left << setw(innerWidth - spaceLeft) << setfill(' ') << m_Title
<< m_Style.borderRight()
<< '\n' << sep;
} else {
ss << renderSeparator(
m_Style.borderTopLeft(),
m_Style.borderTopMid(),
m_Style.borderTopRight(),
m_Style.borderTop()
);
}
if ( m_Headings.numCells() > 0 )
ss << m_Headings.render() << sep;
if ( m_Rows.size() > 0 ) {
for_each(
m_Rows.begin(), --m_Rows.end(),
[&ss, sep]( Row const & row ) -> void {
if ( row.numCells() > 0 ) ss << row.render() << sep;
}
);
ss << m_Rows.back().render();
}
ss << this->renderSeparator(
m_Style.borderBottomLeft(),
m_Style.borderBottomMid(),
m_Style.borderBottomRight(),
m_Style.borderBottom()
);
return ss.str();
}
}
}
#endif