Home | History | Annotate | Download | only in net_internals
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 /**
      6  * TablePrinter is a helper to format a table as ASCII art or an HTML table.
      7  *
      8  * Usage: call addRow() and addCell() repeatedly to specify the data.
      9  *
     10  * addHeaderCell() can optionally be called to specify header cells for a
     11  * single header row.  The header row appears at the top of an HTML formatted
     12  * table, and uses thead and th tags.  In ascii tables, the header is separated
     13  * from the table body by a partial row of dashes.
     14  *
     15  * setTitle() can optionally be used to set a title that is displayed before
     16  * the header row.  In HTML tables, it uses the title class and in ascii tables
     17  * it's between two rows of dashes.
     18  *
     19  * Once all the fields have been input, call toText() to format it as text or
     20  * toHTML() to format it as HTML.
     21  */
     22 var TablePrinter = (function() {
     23   'use strict';
     24 
     25   /**
     26    * @constructor
     27    */
     28   function TablePrinter() {
     29     this.rows_ = [];
     30     this.hasHeaderRow_ = false;
     31     this.title_ = null;
     32     // Number of cells automatically added at the start of new rows.
     33     this.newRowCellIndent_ = 0;
     34   }
     35 
     36   TablePrinter.prototype = {
     37     /**
     38      * Sets the number of blank cells to add after each call to addRow.
     39      */
     40     setNewRowCellIndent: function(newRowCellIndent) {
     41       this.newRowCellIndent_ = newRowCellIndent;
     42     },
     43 
     44     /**
     45      * Starts a new row.
     46      */
     47     addRow: function() {
     48       this.rows_.push([]);
     49       for (var i = 0; i < this.newRowCellIndent_; ++i)
     50         this.addCell('');
     51     },
     52 
     53     /**
     54      * Adds a column to the current row, setting its value to cellText.
     55      *
     56      * @return {!TablePrinterCell} the cell that was added.
     57      */
     58     addCell: function(cellText) {
     59       var r = this.rows_[this.rows_.length - 1];
     60       var cell = new TablePrinterCell(cellText);
     61       r.push(cell);
     62       return cell;
     63     },
     64 
     65     /**
     66      * Sets the title displayed at the top of a table.  Titles are optional.
     67      */
     68     setTitle: function(title) {
     69       this.title_ = title;
     70     },
     71 
     72     /**
     73      * Adds a header row, if not already present, and adds a new column to it,
     74      * setting its contents to |headerText|.
     75      *
     76      * @return {!TablePrinterCell} the cell that was added.
     77      */
     78     addHeaderCell: function(headerText) {
     79       // Insert empty new row at start of |rows_| if currently no header row.
     80       if (!this.hasHeaderRow_) {
     81         this.rows_.splice(0, 0, []);
     82         this.hasHeaderRow_ = true;
     83       }
     84       var cell = new TablePrinterCell(headerText);
     85       this.rows_[0].push(cell);
     86       return cell;
     87     },
     88 
     89     /**
     90      * Returns the maximum number of columns this table contains.
     91      */
     92     getNumColumns: function() {
     93       var numColumns = 0;
     94       for (var i = 0; i < this.rows_.length; ++i) {
     95         numColumns = Math.max(numColumns, this.rows_[i].length);
     96       }
     97       return numColumns;
     98     },
     99 
    100     /**
    101      * Returns the cell at position (rowIndex, columnIndex), or null if there is
    102      * no such cell.
    103      */
    104     getCell_: function(rowIndex, columnIndex) {
    105       if (rowIndex >= this.rows_.length)
    106         return null;
    107       var row = this.rows_[rowIndex];
    108       if (columnIndex >= row.length)
    109         return null;
    110       return row[columnIndex];
    111     },
    112 
    113     /**
    114      * Returns true if searchString can be found entirely within a cell.
    115      * Case insensitive.
    116      *
    117      * @param {string} string String to search for, must be lowercase.
    118      * @return {boolean} True if some cell contains searchString.
    119      */
    120     search: function(searchString) {
    121       var numColumns = this.getNumColumns();
    122       for (var r = 0; r < this.rows_.length; ++r) {
    123         for (var c = 0; c < numColumns; ++c) {
    124           var cell = this.getCell_(r, c);
    125           if (!cell)
    126             continue;
    127           if (cell.text.toLowerCase().indexOf(searchString) != -1)
    128             return true;
    129         }
    130       }
    131       return false;
    132     },
    133 
    134     /**
    135      * Prints a formatted text representation of the table data to the
    136      * node |parent|.  |spacing| indicates number of extra spaces, if any,
    137      * to add between columns.
    138      */
    139     toText: function(spacing, parent) {
    140       var pre = addNode(parent, 'pre');
    141       var numColumns = this.getNumColumns();
    142 
    143       // Figure out the maximum width of each column.
    144       var columnWidths = [];
    145       columnWidths.length = numColumns;
    146       for (var i = 0; i < numColumns; ++i)
    147         columnWidths[i] = 0;
    148 
    149       // If header row is present, temporarily add a spacer row to |rows_|.
    150       if (this.hasHeaderRow_) {
    151         var headerSpacerRow = [];
    152         for (var c = 0; c < numColumns; ++c) {
    153           var cell = this.getCell_(0, c);
    154           if (!cell)
    155             continue;
    156           var spacerStr = makeRepeatedString('-', cell.text.length);
    157           headerSpacerRow.push(new TablePrinterCell(spacerStr));
    158         }
    159         this.rows_.splice(1, 0, headerSpacerRow);
    160       }
    161 
    162       var numRows = this.rows_.length;
    163       for (var c = 0; c < numColumns; ++c) {
    164         for (var r = 0; r < numRows; ++r) {
    165           var cell = this.getCell_(r, c);
    166           if (cell && !cell.allowOverflow) {
    167             columnWidths[c] = Math.max(columnWidths[c], cell.text.length);
    168           }
    169         }
    170       }
    171 
    172       var out = [];
    173 
    174       // Print title, if present.
    175       if (this.title_) {
    176         var titleSpacerStr = makeRepeatedString('-', this.title_.length);
    177         out.push(titleSpacerStr);
    178         out.push('\n');
    179         out.push(this.title_);
    180         out.push('\n');
    181         out.push(titleSpacerStr);
    182         out.push('\n');
    183       }
    184 
    185       // Print each row.
    186       var spacingStr = makeRepeatedString(' ', spacing);
    187       for (var r = 0; r < numRows; ++r) {
    188         for (var c = 0; c < numColumns; ++c) {
    189           var cell = this.getCell_(r, c);
    190           if (cell) {
    191             // Pad the cell with spaces to make it fit the maximum column width.
    192             var padding = columnWidths[c] - cell.text.length;
    193             var paddingStr = makeRepeatedString(' ', padding);
    194 
    195             if (cell.alignRight)
    196               out.push(paddingStr);
    197             if (cell.link) {
    198               // Output all previous text, and clear |out|.
    199               addTextNode(pre, out.join(''));
    200               out = [];
    201 
    202               var linkNode = addNodeWithText(pre, 'a', cell.text);
    203               linkNode.href = cell.link;
    204             } else {
    205               out.push(cell.text);
    206             }
    207             if (!cell.alignRight)
    208               out.push(paddingStr);
    209             out.push(spacingStr);
    210           }
    211         }
    212         out.push('\n');
    213       }
    214 
    215       // Remove spacer row under the header row, if one was added.
    216       if (this.hasHeaderRow_)
    217         this.rows_.splice(1, 1);
    218 
    219       addTextNode(pre, out.join(''));
    220     },
    221 
    222     /**
    223      * Adds a new HTML table to the node |parent| using the specified style.
    224      */
    225     toHTML: function(parent, style) {
    226       var numRows = this.rows_.length;
    227       var numColumns = this.getNumColumns();
    228 
    229       var table = addNode(parent, 'table');
    230       table.setAttribute('class', style);
    231 
    232       var thead = addNode(table, 'thead');
    233       var tbody = addNode(table, 'tbody');
    234 
    235       // Add title, if needed.
    236       if (this.title_) {
    237         var tableTitleRow = addNode(thead, 'tr');
    238         var tableTitle = addNodeWithText(tableTitleRow, 'th', this.title_);
    239         tableTitle.colSpan = numColumns;
    240         tableTitle.classList.add('title');
    241       }
    242 
    243       // Fill table body, adding header row first, if needed.
    244       for (var r = 0; r < numRows; ++r) {
    245         var cellType;
    246         var row;
    247         if (r == 0 && this.hasHeaderRow_) {
    248           row = addNode(thead, 'tr');
    249           cellType = 'th';
    250         } else {
    251           row = addNode(tbody, 'tr');
    252           cellType = 'td';
    253         }
    254         for (var c = 0; c < numColumns; ++c) {
    255           var cell = this.getCell_(r, c);
    256           if (cell) {
    257             var tableCell = addNode(row, cellType, cell.text);
    258             if (cell.alignRight)
    259               tableCell.alignRight = true;
    260             // If allowing overflow on the rightmost cell of a row,
    261             // make the cell span the rest of the columns.  Otherwise,
    262             // ignore the flag.
    263             if (cell.allowOverflow && !this.getCell_(r, c + 1))
    264               tableCell.colSpan = numColumns - c;
    265             if (cell.link) {
    266               var linkNode = addNodeWithText(tableCell, 'a', cell.text);
    267               linkNode.href = cell.link;
    268             } else {
    269               addTextNode(tableCell, cell.text);
    270             }
    271           }
    272         }
    273       }
    274       return table;
    275     }
    276   };
    277 
    278   /**
    279    * Links are only used in HTML tables.
    280    */
    281   function TablePrinterCell(value) {
    282     this.text = '' + value;
    283     this.link = null;
    284     this.alignRight = false;
    285     this.allowOverflow = false;
    286   }
    287 
    288   return TablePrinter;
    289 })();
    290