// Copyright (c) 2010 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /** * Inherit the prototype methods from one constructor into another. */ function inherits(childCtor, parentCtor) { function tempCtor() {}; tempCtor.prototype = parentCtor.prototype; childCtor.superClass_ = parentCtor.prototype; childCtor.prototype = new tempCtor(); childCtor.prototype.constructor = childCtor; }; /** * Sets the width (in pixels) on a DOM node. */ function setNodeWidth(node, widthPx) { node.style.width = widthPx.toFixed(0) + 'px'; } /** * Sets the height (in pixels) on a DOM node. */ function setNodeHeight(node, heightPx) { node.style.height = heightPx.toFixed(0) + 'px'; } /** * Sets the position and size of a DOM node (in pixels). */ function setNodePosition(node, leftPx, topPx, widthPx, heightPx) { node.style.left = leftPx.toFixed(0) + 'px'; node.style.top = topPx.toFixed(0) + 'px'; setNodeWidth(node, widthPx); setNodeHeight(node, heightPx); } /** * Sets the visibility for a DOM node. */ function setNodeDisplay(node, isVisible) { node.style.display = isVisible ? '' : 'none'; } /** * Adds a node to |parentNode|, of type |tagName|. */ function addNode(parentNode, tagName) { var elem = parentNode.ownerDocument.createElement(tagName); parentNode.appendChild(elem); return elem; } /** * Adds |text| to node |parentNode|. */ function addTextNode(parentNode, text) { var textNode = parentNode.ownerDocument.createTextNode(text); parentNode.appendChild(textNode); return textNode; } /** * Adds a node to |parentNode|, of type |tagName|. Then adds * |text| to the new node. */ function addNodeWithText(parentNode, tagName, text) { var elem = parentNode.ownerDocument.createElement(tagName); parentNode.appendChild(elem); addTextNode(elem, text); return elem; } /** * Adds or removes a CSS class to |node|. */ function changeClassName(node, classNameToAddOrRemove, isAdd) { // Multiple classes can be separated by spaces. var currentNames = node.className.split(' '); if (isAdd) { if (!(classNameToAddOrRemove in currentNames)) { currentNames.push(classNameToAddOrRemove); } } else { for (var i = 0; i < currentNames.length; ++i) { if (currentNames[i] == classNameToAddOrRemove) { currentNames.splice(i, 1); break; } } } node.className = currentNames.join(' '); } function getKeyWithValue(map, value) { for (key in map) { if (map[key] == value) return key; } return '?'; } /** * Looks up |key| in |map|, and returns the resulting entry, if there is one. * Otherwise, returns |key|. Intended primarily for use with incomplete * tables, and for reasonable behavior with system enumerations that may be * extended in the future. */ function tryGetValueWithKey(map, key) { if (key in map) return map[key]; return key; } /** * Builds a string by repeating |str| |count| times. */ function makeRepeatedString(str, count) { var out = []; for (var i = 0; i < count; ++i) out.push(str); return out.join(''); } /** * TablePrinter is a helper to format a table as ascii art or an HTML table. * * Usage: call addRow() and addCell() repeatedly to specify the data. * * addHeaderCell() can optionally be called to specify header cells for a * single header row. The header row appears at the top of an HTML formatted * table, and uses thead and th tags. In ascii tables, the header is separated * from the table body by a partial row of dashes. * * setTitle() can optionally be used to set a title that is displayed before * the header row. In HTML tables, it uses the title class and in ascii tables * it's between two rows of dashes. * * Once all the fields have been input, call toText() to format it as text or * toHTML() to format it as HTML. */ function TablePrinter() { this.rows_ = []; this.hasHeaderRow_ = false; this.title_ = null; } /** * Links are only used in HTML tables. */ function TablePrinterCell(value) { this.text = '' + value; this.link = null; this.alignRight = false; this.allowOverflow = false; } /** * Starts a new row. */ TablePrinter.prototype.addRow = function() { this.rows_.push([]); }; /** * Adds a column to the current row, setting its value to cellText. * * @returns {!TablePrinterCell} the cell that was added. */ TablePrinter.prototype.addCell = function(cellText) { var r = this.rows_[this.rows_.length - 1]; var cell = new TablePrinterCell(cellText); r.push(cell); return cell; }; TablePrinter.prototype.setTitle = function(title) { this.title_ = title; }; /** * Adds a header row, if not already present, and adds a new column to it, * setting its contents to |headerText|. * * @returns {!TablePrinterCell} the cell that was added. */ TablePrinter.prototype.addHeaderCell = function(headerText) { // Insert empty new row at start of |rows_| if currently no header row. if (!this.hasHeaderRow_) { this.rows_.splice(0, 0, []); this.hasHeaderRow_ = true; } var cell = new TablePrinterCell(headerText); this.rows_[0].push(cell); return cell; }; /** * Returns the maximum number of columns this table contains. */ TablePrinter.prototype.getNumColumns = function() { var numColumns = 0; for (var i = 0; i < this.rows_.length; ++i) { numColumns = Math.max(numColumns, this.rows_[i].length); } return numColumns; } /** * Returns the cell at position (rowIndex, columnIndex), or null if there is * no such cell. */ TablePrinter.prototype.getCell_ = function(rowIndex, columnIndex) { if (rowIndex >= this.rows_.length) return null; var row = this.rows_[rowIndex]; if (columnIndex >= row.length) return null; return row[columnIndex]; }; /** * Returns a formatted text representation of the table data. * |spacing| indicates number of extra spaces, if any, to add between * columns. */ TablePrinter.prototype.toText = function(spacing) { var numColumns = this.getNumColumns(); // Figure out the maximum width of each column. var columnWidths = []; columnWidths.length = numColumns; for (var i = 0; i < numColumns; ++i) columnWidths[i] = 0; // If header row is present, temporarily add a spacer row to |rows_|. if (this.hasHeaderRow_) { var headerSpacerRow = []; for (var c = 0; c < numColumns; ++c) { var cell = this.getCell_(0, c); if (!cell) continue; var spacerStr = makeRepeatedString('-', cell.text.length); headerSpacerRow.push(new TablePrinterCell(spacerStr)); } this.rows_.splice(1, 0, headerSpacerRow); } var numRows = this.rows_.length; for (var c = 0; c < numColumns; ++c) { for (var r = 0; r < numRows; ++r) { var cell = this.getCell_(r, c); if (cell && !cell.allowOverflow) { columnWidths[c] = Math.max(columnWidths[c], cell.text.length); } } } var out = []; // Print title, if present. if (this.title_) { var titleSpacerStr = makeRepeatedString('-', this.title_.length); out.push(titleSpacerStr); out.push('\n'); out.push(this.title_); out.push('\n'); out.push(titleSpacerStr); out.push('\n'); } // Print each row. var spacingStr = makeRepeatedString(' ', spacing); for (var r = 0; r < numRows; ++r) { for (var c = 0; c < numColumns; ++c) { var cell = this.getCell_(r, c); if (cell) { // Pad the cell with spaces to make it fit the maximum column width. var padding = columnWidths[c] - cell.text.length; var paddingStr = makeRepeatedString(' ', padding); if (cell.alignRight) { out.push(paddingStr); out.push(cell.text); } else { out.push(cell.text); out.push(paddingStr); } out.push(spacingStr); } } out.push('\n'); } // Remove spacer row under the header row, if one was added. if (this.hasHeaderRow_) this.rows_.splice(1, 1); return out.join(''); }; /** * Adds a new HTML table to the node |parent| using the specified style. */ TablePrinter.prototype.toHTML = function(parent, style) { var numRows = this.rows_.length; var numColumns = this.getNumColumns(); var table = addNode(parent, 'table'); table.setAttribute('class', style); var thead = addNode(table, 'thead'); var tbody = addNode(table, 'tbody'); // Add title, if needed. if (this.title_) { var tableTitleRow = addNode(thead, 'tr'); var tableTitle = addNodeWithText(tableTitleRow, 'th', this.title_); tableTitle.colSpan = numColumns; changeClassName(tableTitle, 'title', true); } // Fill table body, adding header row first, if needed. for (var r = 0; r < numRows; ++r) { var cellType; var row; if (r == 0 && this.hasHeaderRow_) { row = addNode(thead, 'tr'); cellType = 'th'; } else { row = addNode(tbody, 'tr'); cellType = 'td'; } for (var c = 0; c < numColumns; ++c) { var cell = this.getCell_(r, c); if (cell) { var tableCell = addNode(row, cellType, cell.text); if (cell.alignRight) tableCell.alignRight = true; // If allowing overflow on the rightmost cell of a row, // make the cell span the rest of the columns. Otherwise, // ignore the flag. if (cell.allowOverflow && !this.getCell_(r, c + 1)) tableCell.colSpan = numColumns - c; if (cell.link) { var linkNode = addNodeWithText(tableCell, 'a', cell.text); linkNode.href = cell.link; } else { addTextNode(tableCell, cell.text); } } } } return table; };