Saiku导出excel指标列无线条以及0与空值显示问题
描述: 数据库中字段值为0 ,与数据库中字段值为 null 时 ,saiku会将为0 以及为 null 的数据都不展示出来,但是我们其实希望数据库中为0 的数据展示位0 ,为null的时候就是null 可以不展示。
table展示0与null有两个点需要处理:
1. 展示在页面上的table数据
2.导出的excel中的数据(导出excel还有个格式问题,指标信息没有线条分割,维度信息正常)
1.我们先来解决一下怎么让(0与null)数据在页面中展示正常
在saiku的SaikuTableRenderer.js文件中 添加為0的數據顯示問題 (修改後為0的DATA_CELL也會正常顯示為0),line825-828
- if (val == " " || val == '') {
- val=0;
- }
添加位置可参考下图: (SaikuTableRenderer.js)
后台查询接口对于0值返回的结果是 " ",所以我就用了以上判断,可自己debug调试哦。
2.导出excel数据时问题处理 后台导出excel使用POI插件
>>>添加线条:
ExcelWorksheetBuilder.java ---> line 154 添加 setCellBordersColor(numberCS);
添加位置可参考下图: (ExcelWorksheetBuilder.java)
>>>处理0与null值的问题
1.在FlattenedCellSetFormatter.java中修改cellvalue的值为0.
(这里if里面的判断条件是我debug出来的哦,当数据为空的时候,cellvalue的值时空的,当数据为0的时候,cellvalue的值是 | |style=white ,当数据有其他值的时候(例如33),cellvalue的值为 |33 |style=white)
2.接下来就是导出时的样式设置了
在ExcelWorksheetBuilder.java中添加新的样式设定,专门对于cellvalue的值为0.(红色框框中为添加内容)
3.在 ExcelWorksheetBuilder.java的 initCellStyles()方法中添加对 number0CS的初始化
4.在 ExcelWorksheetBuilder.java的addExcelTableRows()方法中对0值的添加自定义的样式处理。
(这里说明一下为什么还要判断一下 formatString的值是否为 #,### ,因为有其他样式的时候0值不会被忽略掉,比如 formatString= #,#00.00% 这种百分比形式时,0值对应的值就会为 0.00%)
红色框框为改动后的内容,之前这个位置的代码为: applyCellFormating(cell,dataCell);
至此,我们对于table渲染0值与空值的问题就处理好了,导出的excel指标数据也会有线条啦~ 重新打包编译,启动saiku即可~
ps: 顺便提一下iframe嵌入saiku url的问题,会有那种偶然性的 bug,大概是网络问题 导致js加载出问题了,然后嵌入的iframe展示高度就会变得很短。
处理方案: 在saiku的 saiku.table.css中設置.workspace_results 樣式的最小高度為708 min-height
最后提供完整的文件信息:
SaikuTableRenderer.js
- function SaikuTableRenderer(data, options) {
- this._data = data;
- this._options = _.extend({}, SaikuRendererOptions, options);
- }
-
- function getAxisLevelsName(data, axisName) {
- var queryData = data.query.queryModel.axes[axisName].hierarchies;
- var len = queryData.length;
- var arrLevels = [];
-
- for (var i = 0; i < len; i++) {
- for (var level in queryData[i].levels) {
- if (queryData[i].levels.hasOwnProperty(level)) {
- if (Settings.COLUMN_TITLE_TABLE_USE_LEVEL_CAPTION_NAME) {
- arrLevels.push(queryData[i].levels[level].caption);
- }
- else {
- arrLevels.push(level);
- }
- }
- }
- }
-
- return arrLevels;
- }
-
- function setStyleNegativeNumber(value) {
- var className = '';
-
- if (Settings.STYLE_NEGATIVE_NUMBER && parseFloat(value) < 0) {
- className = ' style_negative_number ';
- }
-
- return className;
- }
-
- function getAxisSize(data, axisName) {
- var queryData = data.query.queryModel.axes[axisName].hierarchies;
- var len = queryData.length;
- var axisSize = 0;
-
- for (var i = 0; i < len; i++) {
- axisSize += _.size(queryData[i].levels);
- }
-
- return axisSize;
- }
-
- function getDomColumnsLevelsName(htmlObject) {
- var $htmlObject = $(htmlObject.closest('.workspace')
- .find('.workspace_fields')
- .find('.columns.axis_fields')
- .find('.hierarchy')
- .find('.d_level'));
- var arrLevels = [];
-
- $.each($htmlObject, function(key, level) {
- if ($(level).attr('style') === 'display: list-item;') {
- if (Settings.COLUMN_TITLE_TABLE_USE_LEVEL_CAPTION_NAME) {
- arrLevels.push($(level).find('.level').attr('title'));
- }
- else {
- arrLevels.push($(level).find('.level').attr('level'));
- }
- }
- });
-
- return arrLevels;
- }
-
- /*table render method*/
- SaikuTableRenderer.prototype.render = function(data, options) {
- var self = this;
- if (data) {
- this._data = data;
- }
- if (options) {
- this._options = _.extend({}, SaikuRendererOptions, options);
- }
-
- if (typeof this._data == "undefined") {
- return;
- }
-
- if (this._data != null && this._data.error != null) {
- return;
- }
- if (this._data == null || (this._data.cellset && this._data.cellset.length === 0)) {
- return;
- }
-
- this.hideEmpty = this._options.hideEmpty;
-
- if (this._options.htmlObject) {
- // $(this._options.htmlObject).stickyTableHeaders("destroy");
-
- // in case we have some left over scrollers
- if (self._options.hasOwnProperty('batch')) {
- $(self._options.htmlObject).parent().parent().unbind('scroll');
- }
-
- _.defer(function(that) {
- if (self._options.hasOwnProperty('batch') && !self._options.hasOwnProperty('batchSize')) {
- self._options['batchSize'] = 1000;
- }
-
- // the key method to render data by table form. 20190423
- var html = self.internalRender(self._data, self._options);
- $(self._options.htmlObject).html(html);
- // Render the totals summary
- $('#totals_summary').remove(); // Remove one previous totals div, if present
- $(self._options.htmlObject).after(self.renderSummary(data)); // Render the new summary
-
- // $(self._options.htmlObject).stickyTableHeaders( { container: self._options.htmlObject.parent().parent(), fixedOffset: self._options.htmlObject.parent().parent().offset().top });
-
- _.defer(function(that) {
- if (self._options.hasOwnProperty('batch') && self._options.hasBatchResult) {
- var batchRow = 0;
- var batchIsRunning = false;
- var batchIntervalSize = self._options.hasOwnProperty('batchIntervalSize') ? self._options.batchIntervalSize : 20;
- var batchIntervalTime = self._options.hasOwnProperty('batchIntervalTime') ? self._options.batchIntervalTime : 20;
-
- var len = self._options.batchResult.length;
-
- var batchInsert = function() {
- // maybe add check for reach table bottom - ($('.workspace_results').scrollTop() , $('.workspace_results table').height()
- if (!batchIsRunning && len > 0 && batchRow < len) {
- batchIsRunning = true;
- var batchContent = "";
- var startb = batchRow;
- for (var i = 0; batchRow < len && i < batchIntervalSize ; i++, batchRow++) {
- batchContent += self._options.batchResult[batchRow];
- }
- if (batchRow > startb) {
- $(self._options.htmlObject).append( $(batchContent));
- }
- batchIsRunning = false;
- }
- if (batchRow >= len) {
- $(self._options.htmlObject).parent().parent().unbind('scroll');
- }
- };
-
- var lazyBatchInsert = _.debounce(batchInsert, batchIntervalTime);
- $(self._options.htmlObject).parent().parent().scroll(function () {
- lazyBatchInsert();
- });
- }
- });
- return html;
- });
- } else {
- var html = this.internalRender(this._data, self._options);
- return html;
- }
-
- };
-
- SaikuTableRenderer.prototype.clear = function(data, options) {
- var self = this;
- if (this._options && this._options.htmlObject && this._options.hasOwnProperty('batch')) {
- $(self._options.htmlObject).parent().parent().unbind('scroll');
- }
-
- };
-
- SaikuTableRenderer.prototype.processData = function(data, options) {
- this._hasProcessed = true;
- };
-
- function genTotalDataCells(currentIndex, cellIndex, scanSums, scanIndexes, lists) {
- var contents = '';
- var lists = lists[ROWS];
-
- for (var i = scanSums.length - 1; i >= 0; i--) {
- if (currentIndex == scanSums[i]) {
- var currentListNode = lists[i][scanIndexes[i]];
- for (var m = 0; m < currentListNode.cells.length; m++) {
- contents += '<td class="data total">' + currentListNode.cells[m][cellIndex].value + '</td>';
- }
-
- scanIndexes[i]++;
- if (scanIndexes[i] < lists[i].length)
- scanSums[i] += lists[i][scanIndexes[i]].width;
- }
- }
-
- return contents;
- }
-
- function genTotalHeaderCells(currentIndex, bottom, scanSums, scanIndexes, lists, wrapContent) {
- var contents = '';
- for (var i = bottom; i >= 0; i--) {
- if (currentIndex == scanSums[i]) {
- var currentListNode = lists[i][scanIndexes[i]];
- var cssClass;
- if (i == 0 && bottom == 1)
- cssClass = "col";
- else if (i == bottom)
- cssClass = "col_total_corner";
- else if (i == bottom - 1 && currentListNode.captions)
- cssClass = "col_total_first";
- else cssClass = "col_null";
-
- for (var m = 0; m < currentListNode.cells.length; m++) {
- var text = ' ';
- if (bottom == lists.length - 1) {
- if (currentListNode.captions) {
- text = lists[i][scanIndexes[i]].captions[m];
- }
- if (i == 0 && scanIndexes[i] == 0) {
- if (currentListNode.captions)
- text += " ";
- else text = "";
- text += (wrapContent ? "<span class='i18n'>Grand Total</span>" : "Grand Total");
- }
- }
- contents += '<th class="' + cssClass + '">'
- + (wrapContent ? '<div>' + text + '</div>' : text ) + '</th>';
- }
- scanIndexes[i]++;
- if (scanIndexes[i] < lists[i].length)
- scanSums[i] += lists[i][scanIndexes[i]].width;
- }
- }
- return contents;
- }
-
- function totalIntersectionCells(currentIndex, bottom, scanSums, scanIndexes, lists) {
- var contents = '';
- for (var i = bottom; i >= 0; i--) {
- if (currentIndex == scanSums[i]) {
- var currentListNode = lists[i][scanIndexes[i]];
- var cssClass = "data total";
- for (var m = 0; m < currentListNode.cells.length; m++) {
- var text = ' ';
- contents += '<td class="' + cssClass + '">' + text + '</td>';
- }
- scanIndexes[i]++;
- if (scanIndexes[i] < lists[i].length)
- scanSums[i] += lists[i][scanIndexes[i]].width;
- }
- }
- return contents;
- }
-
- function isNextTotalsRow(currentIndex, scanSums, scanIndexes, totalsLists, wrapContent) {
- var colLists = totalsLists[COLUMNS];
- var colScanSums = scanSums[COLUMNS];
- var colScanIndexes = scanIndexes[COLUMNS];
- var bottom = colLists.length - 2;
- var contents = -1;
- for (var i = bottom; i >= 0; i--) {
- if (currentIndex == colScanSums[i]) {
- for (var m = 0; m < colLists[i][colScanIndexes[i]].cells.length; m++) {
- contents += '<tr>';
- for (var j = 0; j <= bottom; j++) {
- var cssClass;
- var text = ' ';
- if (i == 0 && j == 0)
- cssClass = 'row';
- else if (i == j + 1){
- cssClass = 'row_total_corner';
- return j;
- }
- else if (i == j && colLists[i][colScanIndexes[i]].captions) {
- cssClass = 'row_total_first';
- } else if (i < j + 1)
- cssClass = 'row_total';
- else
- cssClass = 'row_null';
- if (j == bottom ) {
- if (colLists[i][colScanIndexes[i]].captions) {
- text = colLists[i][colScanIndexes[i]].captions[m];
- }
- if (i == 0 && colScanIndexes[i] == 0) {
- if (colLists[i][colScanIndexes[i]].captions)
- text += " ";
- else text = "";
- text += (wrapContent ? "<span class='i18n'>Grand Total</span>" : "Grand Total");
- }
- }
-
- }
- }
- }
- }
- return -1;
- }
-
- function genTotalHeaderRowCells(currentIndex, scanSums, scanIndexes, totalsLists, wrapContent) {
- var colLists = totalsLists[COLUMNS];
- var colScanSums = scanSums[COLUMNS];
- var colScanIndexes = scanIndexes[COLUMNS];
- var bottom = colLists.length - 2;
- var contents = '';
- for (var i = bottom; i >= 0; i--) {
- if (currentIndex == colScanSums[i]) {
- for (var m = 0; m < colLists[i][colScanIndexes[i]].cells.length; m++) {
- contents += '<tr>';
- for (var j = 0; j <= bottom; j++) {
- var cssClass;
- var text = ' ';
- if (i == 0 && j == 0)
- cssClass = 'row';
- else if (i == j + 1)
- cssClass = 'row_total_corner';
- else if (i == j && colLists[i][colScanIndexes[i]].captions) {
- cssClass = 'row_total_first';
- } else if (i < j + 1)
- cssClass = 'row_total';
- else
- cssClass = 'row_null';
- if (j == bottom ) {
- if (colLists[i][colScanIndexes[i]].captions) {
- text = colLists[i][colScanIndexes[i]].captions[m];
- }
- if (i == 0 && colScanIndexes[i] == 0) {
- if (colLists[i][colScanIndexes[i]].captions)
- text += " ";
- else text = "";
- text += (wrapContent ? "<span class='i18n'>Grand Total</span>" : "Grand Total");
- }
- }
- contents += '<th class="' + cssClass + '">'
- + (wrapContent ? '<div>' + text + '</div>' : text ) + '</th>';
-
- }
-
- var scanIndexes = {};
- var scanSums = {};
-
- if (totalsLists[ROWS]) {
- for (var z = 0; z < totalsLists[ROWS].length; z++) {
- scanIndexes[z] = 0;
- scanSums[z] = totalsLists[ROWS][z][scanIndexes[z]].width;
- }
- }
-
- for (var k = 0; k < colLists[i][colScanIndexes[i]].cells[m].length; k++) {
- contents += '<td class="data total">' + colLists[i][colScanIndexes[i]].cells[m][k].value + '</td>';
-
- if (totalsLists[ROWS]) {
- contents += totalIntersectionCells(k + 1, totalsLists[ROWS].length - 1, scanSums, scanIndexes, totalsLists[ROWS]);
- }
- }
-
- contents += '</tr>';
- }
-
- colScanIndexes[i]++;
-
- if (colScanIndexes[i] < colLists[i].length) {
- colScanSums[i] += colLists[i][colScanIndexes[i]].width;
- }
- }
- }
- return contents;
- }
-
- var ROWS = "ROWS";
- var COLUMNS = "COLUMNS";
-
- function nextParentsDiffer(data, row, col) {
- while (row-- > 0) {
- if (data[row][col].properties.uniquename != data[row][col + 1].properties.uniquename)
- return true;
- }
- return false;
- }
-
-
- function topParentsDiffer(data, row, col) {
- while (col-- > 0)
- if (data[row][col].properties.uniquename != data[row - 1][col].properties.uniquename)
- return true;
- return false;
- }
-
- /**
- * This function is intended to traverse the totals arrays and cleanup empty
- * totals. This will optimize the query result on screen, displaying just the
- * needed cells.
- * @param dirs The direction array ['ROWS', 'COLUMNS']
- * @param totalsLists The totals from allData.rowTotalsLists and allData.colTotalsLists.
- */
- function cleanupTotals(dirs, totalsLists) {
- // For each direction (ROWS/COLUMNS)
- for (var dirIndex = 0; dirIndex < dirs.length; dirIndex++) {
- var dir = dirs[dirIndex];
-
- // If there are defined totals
- if (totalsLists[dir]) {
- var isEmpty = true; // A flag to indicate if this total is empty
- for (var row = 0; row < totalsLists[dir].length; row++) {
- var totalsInfoArray = totalsLists[dir][row];
- for (var totalIndex = 0; totalIndex < totalsInfoArray.length; totalIndex++) {
- var cells = totalsLists[dir][row][totalIndex].cells;
- for (var cellIndex = 0; cellIndex < cells.length; cellIndex++) {
- var cellArray = cells[cellIndex];
- // For each total cell
- for (var i = 0; i < cellArray.length; i++) {
- var cell = cellArray[i];
- // If it contains a value different from empty
- if (cell.value !== '-') {
- isEmpty = false; // So, this total is not empty
- }
- }
- }
- }
- }
-
- if (isEmpty) { // If this total is empty
- totalsLists[dir] = null; // Remove it
- }
- }
- }
- }
-
- /*the main method to render data by table form. 20190423*/
- SaikuTableRenderer.prototype.internalRender = function(allData, options) {
- var tableContent = "";
- var rowContent = "";
- var data = allData.cellset;
-
- var newRowContent = '';
- var arrRowData = [];
- var objRowData = [];
-
- var table = data ? data : [];
- var colSpan;
- var colValue;
- var isHeaderLowestLvl;
- var isBody = false;
- var firstColumn;
- var isLastColumn, isLastRow;
- var nextHeader;
- var processedRowHeader = false;
- var lowestRowLvl = 0;
- var rowGroups = [];
- var batchSize = null;
- var batchStarted = false;
- var isColHeader = false, isColHeaderDone = false;
- var resultRows = [];
- var wrapContent = true;
- if (options) {
- batchSize = options.hasOwnProperty('batchSize') ? options.batchSize : null;
- wrapContent = options.hasOwnProperty('wrapContent') ? options.wrapContent : true;
- }
- var totalsLists = {};
- totalsLists[COLUMNS] = allData.rowTotalsLists;
- totalsLists[ROWS] = allData.colTotalsLists;
-
- var scanSums = {};
- var scanIndexes = {};
-
- var dirs = [ROWS, COLUMNS];
-
- var hasMeasures = allData.query && allData.query.queryModel && allData.query.queryModel.details
- ? allData.query.queryModel.details.measures.length
- : 0;
-
- if (typeof this._options.htmlObject === 'object' &&
- Settings.ALLOW_AXIS_COLUMN_TITLE_TABLE &&
- hasMeasures > 0 &&
- allData.query.type === 'QUERYMODEL' &&
- allData.query.queryModel.details.axis === 'COLUMNS' &&
- allData.query.queryModel.details.location === 'BOTTOM') {
-
- var arrColumnTitleTable = getAxisLevelsName(allData, COLUMNS);
- var arrDomColumnTitleTable = getDomColumnsLevelsName(this._options.htmlObject);
- var colspanColumnTitleTable = getAxisSize(allData, ROWS);
- var auxColumnTitleTable = 0;
-
- if (arrColumnTitleTable.length === arrDomColumnTitleTable.length) {
- arrColumnTitleTable = arrDomColumnTitleTable;
- }
- else {
- arrColumnTitleTable = _.intersection(arrDomColumnTitleTable, arrColumnTitleTable);
- }
- }
-
- for (var i = 0; i < dirs.length; i++) {
- scanSums[dirs[i]] = new Array();
- scanIndexes[dirs[i]] = new Array();
- }
-
- // Here we cleaup the empty totals
- cleanupTotals(dirs, totalsLists);
-
- if (totalsLists[COLUMNS]) {
- for (var i = 0; i < totalsLists[COLUMNS].length; i++) {
- scanIndexes[COLUMNS][i] = 0;
- scanSums[COLUMNS][i] = totalsLists[COLUMNS][i][scanIndexes[COLUMNS][i]].width;
- }
- }
-
- var headerFlag=true;// add this flag to solve the bug when same data to merge。 20190423
- var cubename = allData.query.cube.name;
- for (var row = 0, rowLen = table.length; row < rowLen; row++) {
- var rowShifted = row - allData.topOffset;
- colSpan = 1;
- colValue = "";
- isHeaderLowestLvl = false;
- isLastColumn = false;
- isLastRow = false;
- isColHeader = false;
- var headerSame = false;
-
- if (totalsLists[ROWS]) {
- for (var i = 0; i < totalsLists[ROWS].length; i++) {
- scanIndexes[ROWS][i] = 0;
- scanSums[ROWS][i] = totalsLists[ROWS][i][scanIndexes[ROWS][i]].width;
- }
- }
-
- rowWithOnlyEmptyCells = true; //默认行数据为空时不展展示,如果改回来 将此条值置为true即可
-
- //var paramsURI = Saiku.URLParams.paramsURI(); //get the param from url
- //var showEmptyRows = paramsURI.showEmptyRows;
- //if(showEmptyRows != null && showEmptyRows != undefined && showEmptyRows != '' && showEmptyRows=='yes') {
- if(cubename == "SummaryKPI_2018_ext" ||cubename == "SummaryKPI_2019_ext" ||cubename == "SummaryKPI_2019_Dynamic"){
- rowWithOnlyEmptyCells = false; //設置行數據為空的時候也顯示數據! 20190425 for summaryKPI Data
- }
- rowContent = "<tr>";
- var header = null;
-
- if (row === 0) {
- rowContent = "<thead>" + rowContent;
- }
-
- if (typeof this._options.htmlObject === 'object' &&
- Settings.ALLOW_AXIS_COLUMN_TITLE_TABLE &&
- hasMeasures > 0 &&
- allData.query.type === 'QUERYMODEL' &&
- allData.query.queryModel.details.axis === 'COLUMNS' &&
- allData.query.queryModel.details.location === 'BOTTOM' &&
- auxColumnTitleTable < arrColumnTitleTable.length) {
-
- rowContent += '<th class="row_header" style="text-align: right;" colspan="' + colspanColumnTitleTable + '" title="' + arrColumnTitleTable[auxColumnTitleTable] + '">'
- + (wrapContent ? '<div>' + arrColumnTitleTable[auxColumnTitleTable] + '</div>' : arrColumnTitleTable[auxColumnTitleTable])
- + '</th>';
-
- auxColumnTitleTable += 1;
- }
-
-
- //循環遍歷data 將帶小數的年、月去掉小數位; 例如 2018.0 轉換為 2018
- for (var col = 0, colLen = table[row].length; col < colLen; col++) {
- var tmpRow = data[row][col];
- if(tmpRow.properties.hierarchy=="[年].[年]" || tmpRow.properties.hierarchy=="[月].[月]"){
- var tmpValue = tmpRow.value;
- if(tmpValue != null && tmpValue.indexOf('.')>-1){
- tmpValue = tmpValue.substr(0,tmpValue.indexOf('.'));//去掉小數位
- data[row][col].value = tmpValue;
- }
- }
- }
-
-
-
- for (var col = 0, colLen = table[row].length; col < colLen; col++) {
-
- var colShifted = col - allData.leftOffset;
- header = data[row][col];
-
- if (header.type === "COLUMN_HEADER") {
- isColHeader = true;
- }
-
- // If the cell is a column header and is null (top left of table)
- if (header.type === "COLUMN_HEADER" && header.value === "null" && (firstColumn == null || col < firstColumn)) {
- if (((!Settings.ALLOW_AXIS_COLUMN_TITLE_TABLE || (Settings.ALLOW_AXIS_COLUMN_TITLE_TABLE && allData.query.queryModel.details.location !== 'BOTTOM')) || hasMeasures === 0) ||
- allData.query.type === 'MDX') {
- rowContent += '<th class="all_null"> </th>';
- }
- } // If the cell is a column header and isn't null (column header of table)
- else if (header.type === "COLUMN_HEADER") {
- if (firstColumn == null) {
- firstColumn = col;
- }
- if (table[row].length == col+1)
- isLastColumn = true;
- else
- nextHeader = data[row][col+1];
-
-
- if (isLastColumn) {
- // Last column in a row...
- if (header.value == "null") {
- rowContent += '<th class="col_null"> </th>';
- } else {
- if (totalsLists[ROWS])
- colSpan = totalsLists[ROWS][row + 1][scanIndexes[ROWS][row + 1]].span;
- rowContent += '<th class="col" style="text-align: center;" colspan="' + colSpan + '" title="' + header.value + '">'
- + (wrapContent ? '<div rel="' + row + ":" + col +'">' + header.value + '</div>' : header.value)
- + '</th>';
- }
-
- } else {
- // All the rest...
- var groupChange = (col > 1 && row > 1 && !isHeaderLowestLvl && col > firstColumn) ?
- data[row-1][col+1].value != data[row-1][col].value || data[row-1][col+1].properties.uniquename != data[row-1][col].properties.uniquename
- : false;
-
- var maxColspan = colSpan > 999 ? true : false;
- if (header.value != nextHeader.value || nextParentsDiffer(data, row, col) || isHeaderLowestLvl || groupChange || maxColspan) {
- if (header.value == "null") {
- rowContent += '<th class="col_null" colspan="' + colSpan + '"> </th>';
- } else {
- if (totalsLists[ROWS])
- colSpan = totalsLists[ROWS][row + 1][scanIndexes[ROWS][row + 1]].span;
- rowContent += '<th class="col" style="text-align: center;" colspan="' + (colSpan == 0 ? 1 : colSpan) + '" title="' + header.value + '">'
- + (wrapContent ? '<div rel="' + row + ":" + col +'">' + header.value + '</div>' : header.value)
- + '</th>';
- }
- colSpan = 1;
- } else {
- colSpan++;
- }
- }
- if (totalsLists[ROWS])
- rowContent += genTotalHeaderCells(col - allData.leftOffset + 1, row + 1, scanSums[ROWS], scanIndexes[ROWS], totalsLists[ROWS], wrapContent);
- } // If the cell is a row header and is null (grouped row header)
- else if (header.type === "ROW_HEADER" && header.value === "null") {
- rowContent += '<th class="row_null"> </th>';
- } // If the cell is a row header and isn't null (last row header)
- else if (header.type === "ROW_HEADER") {
- if (lowestRowLvl == col)
- isHeaderLowestLvl = true;
- else
- nextHeader = data[row][col+1];
-
- var previousRow = data[row - 1];
- var nextRow = data[row + 1];
- // when same set fixed value is false ,It means the same data will not merge。table data will show row by row.20190423
- //var same=false;
-
- /*judge the current value and previousRow value,
- if equals ,all set comeback,set the headerFlag is true,
- we can judge the data as usual. 20190423*/
- if(header.value !== previousRow[col].value){
- headerFlag =true;
- }
-
- /*judge the tableContent include value or not, if include ,
- set the headerFlag value is true to avoid repeat datas showed in table.20190423*/
- if(tableContent.indexOf(header.value) > -1 ){
- headerFlag =true;
- }
-
- /*add headerFlag to judge the data is same ,then control the data merge wheather or not.20190423 */
- var same = !headerSame && !isHeaderLowestLvl && (col == 0 || !topParentsDiffer(data, row, col)) && header.value === previousRow[col].value && headerFlag;
- headerSame = !same;
- var sameAsPrevValue = false;
- if(Settings.ALLOW_TABLE_DATA_COLLAPSE){
- if (row > 0 && row < rowLen - 1) {
- if (totalsLists[ROWS] == null || (col <= colLen - totalsLists[ROWS].length - 1)) {
- var checkOther = true;
- if (totalsLists[COLUMNS] && rowShifted >= 0 && col <= isNextTotalsRow(rowShifted + 1, scanSums, scanIndexes, totalsLists, wrapContent)) {
- sameAsPrevValue = true;
- checkOther = false;
- }
- if (checkOther && nextRow[col].value == header.value) {
- if (col > 0) {
- for (var j = 0; j < col; j++) {
- if (nextRow[j].value == data[row][j].value) {
- sameAsPrevValue = true;
- } else {
- sameAsPrevValue = false;
- break;
- }
- }
- } else {
- sameAsPrevValue = true;
- }
- }
- }
- } else if(row > 0 && row == rowLen - 1) {
- if (totalsLists[COLUMNS] && rowShifted >= 0 && col <= isNextTotalsRow(rowShifted + 1, scanSums, scanIndexes, totalsLists, wrapContent)) {
- sameAsPrevValue = true;
- }
- }
- }
- var value = (same ? "<div> </div>" : '<div rel="' + row + ":" + col + '">'
- + (sameAsPrevValue && Settings.ALLOW_TABLE_DATA_COLLAPSE ? '<span class="expander expanded" style="cursor: pointer;">▼</span>' : '' ) + header.value + '</div>');
- if (!wrapContent) {
- value = (same ? " " : header.value );
- }
- var tipsy = "";
- /* var tipsy = ' original-title="';
- if (!same && header.metaproperties) {
- for (key in header.metaproperties) {
- if (key.substring(0,1) != "$" && key.substring(1,2).toUpperCase() != key.substring(1,2)) {
- tipsy += "<b>" + safe_tags_replace(key) + "</b> : " + safe_tags_replace(header.metaproperties[key]) + "<br>";
- }
- }
- }
- tipsy += '"';
- */
- var cssclass = (same ? "row_null" : "row");
- var colspan = 0;
-
- if (!isHeaderLowestLvl && (typeof nextHeader == "undefined" || nextHeader.value === "null")) {
- colspan = 1;
- var group = header.properties.dimension;
- var level = header.properties.level;
- var groupWidth = (group in rowGroups ? rowGroups[group].length - rowGroups[group].indexOf(level) : 1);
- for (var k = col + 1; colspan < groupWidth && k <= (lowestRowLvl+1) && data[row][k] !== "null"; k++) {
- colspan = k - col;
- }
- col = col + colspan -1;
- }
-
- /*when the content is to long ,we will set new line to show it.*/
- // eg value: <div rel="3:0">新業務及保單行政部</div>
- if(cssclass == "row" && value.length>0){
- var startPos = value.indexOf('>'); //find start position of the tag. eg: <div rel="3:0">
- var endPos = value.lastIndexOf('<'); //find end position of the tag. eg: </div>
- var tmpValue = value.substr( startPos+1 ,endPos-startPos-1); // get the content value. eg: 新業務及保單行政部
- //将value值每隔40个字自动加上换行符
- //each 40 character add one <br> tag to get new line.
- if(tmpValue.length>120){
- tmpValue = tmpValue.substr(0,40)+"<br/>"+tmpValue.substr(40,40)+"<br/>"+tmpValue.substr(80,40)+"<br/>"+tmpValue.substr(120,tmpValue.length-120);
- }else if(tmpValue.length>80){
- tmpValue = tmpValue.substr(0,40)+"<br/>"+tmpValue.substr(40,40)+"<br/>"+tmpValue.substr(80,tmpValue.length-80);
- }else if(tmpValue.length>40){
- tmpValue = tmpValue.substr(0,40)+"<br/>"+tmpValue.substr(40,tmpValue.length-40);
- }
-
- // compared with old value, this value only add <br> tag for show data in table more beautiful.
- value = value.substr(0,startPos+1) + tmpValue + value.substr(endPos,value.length-endPos);
- }
- rowContent += '<th class="' + cssclass + '" ' + (colspan > 0 ? ' colspan="' + colspan + '"' : "") + tipsy + '>' + value + '</th>';
- }
- else if (header.type === "ROW_HEADER_HEADER") {
- var hierName = function(data) {
- var hier = data.properties.hierarchy;
- var name = hier.replace(/[\[\]]/gi, '').split('.')[1]
- ? hier.replace(/[\[\]]/gi, '').split('.')[1]
- : hier.replace(/[\[\]]/gi, '').split('.')[0];
-
- return name;
- };
- var arrPosRowData = [];
-
- if (_.contains(arrRowData, header.value)) {
- for (var i = 0; i < arrRowData.length; i++) {
- if (arrRowData[i] === header.value) {
- arrPosRowData.push(i);
- }
- }
-
- arrPosRowData.push(col);
- }
-
- rowContent += '<th class="row_header">' + (wrapContent ? '<div>' + header.value + '</div>' : header.value) + '</th>';
-
- arrRowData.push(header.value);
- objRowData.push({
- name: header.value,
- hierName: hierName(header) + '/' + header.value
- });
-
- isHeaderLowestLvl = true;
- processedRowHeader = true;
- lowestRowLvl = col;
- if (header.properties.hasOwnProperty("dimension")) {
- var group = header.properties.dimension;
- if (!(group in rowGroups)) {
- rowGroups[group] = [];
- }
- rowGroups[group].push(header.properties.level);
- }
-
- if (arrPosRowData.length > 0) {
- var aux = 0;
-
- rowContent = '<tr>';
-
- if (row === 0) {
- rowContent = '<thead>' + rowContent;
- }
-
- for (var i = 0; i < objRowData.length; i++) {
- if (arrPosRowData[aux] === i) {
- newRowContent += '<th class="row_header">' + (wrapContent ? '<div>' + objRowData[i].hierName + '</div>' : objRowData[i].hierName) + '</th>';
- aux += 1;
- }
- else {
- newRowContent += '<th class="row_header">' + (wrapContent ? '<div>' + objRowData[i].name + '</div>' : objRowData[i].name) + '</th>';
- }
- }
-
- rowContent += newRowContent;
- }
- } // If the cell is a normal data cell
- else if (header.type === "DATA_CELL") {
- batchStarted = true;
- var color = "";
- var val = _.isEmpty(header.value) ? Settings.EMPTY_VALUE_CHARACTER : header.value;
- var arrow = "";
-
- if (header.properties.hasOwnProperty('image')) {
- var img_height = header.properties.hasOwnProperty('image_height') ? " height='" + header.properties.image_height + "'" : "";
- var img_width = header.properties.hasOwnProperty('image_width') ? " width='" + header.properties.image_width + "'" : "";
- val = "<img " + img_height + " " + img_width + " style='padding-left: 5px' src='" + header.properties.image + "' border='0'>";
- }
-
- // Just apply formatting to non-empty cells
- if (val !== '-' && val !== '' && header.properties.hasOwnProperty('style')) {
- color = " style='background-color: " + header.properties.style + "' ";
- }
- if (header.properties.hasOwnProperty('link')) {
- val = "<a target='__blank' href='" + header.properties.link + "'>" + val + "</a>";
- }
- if (header.properties.hasOwnProperty('arrow')) {
- arrow = "<img height='10' width='10' style='padding-left: 5px' src='./images/arrow-" + header.properties.arrow + ".gif' border='0'>";
- }
-
- if (val !== '-' && val !== '') {
- rowWithOnlyEmptyCells = false;
- }
-
- //设置saiku为0的数据正常显示0 ,不显示为空
- if (val == " " || val == '') {
- val=0;
- }
-
- rowContent += '<td class="data" ' + color + '>'
- + (wrapContent ? '<div class="datadiv '+ setStyleNegativeNumber(header.properties.raw) + '" alt="' + header.properties.raw + '" rel="' + header.properties.position + '">' : "")
- + val + arrow
- + (wrapContent ? '</div>' : '') + '</td>';
- if (totalsLists[ROWS])
- rowContent += genTotalDataCells(colShifted + 1, rowShifted, scanSums[ROWS], scanIndexes[ROWS], totalsLists, wrapContent);
- }
- }
- rowContent += "</tr>";
-
- // Change it to let hideEmpty true by default
- if (options.hideEmpty && header.type === "DATA_CELL" && rowWithOnlyEmptyCells) {
- /*when data_cell is null,set the headerFlag is false ,
- to fix the problem data merge inccrrect.
- */
- headerFlag=false;
- rowContent = '';
- }
-
- var totals = "";
- if (totalsLists[COLUMNS] && rowShifted >= 0) {
- totals += genTotalHeaderRowCells(rowShifted + 1, scanSums, scanIndexes, totalsLists, wrapContent);
- }
- if (batchStarted && batchSize) {
- if (row <= batchSize) {
- if (!isColHeader && !isColHeaderDone) {
- tableContent += "</thead><tbody>";
- isColHeaderDone = true;
- }
- tableContent += rowContent;
- if (totals.length > 0) {
- tableContent += totals;
- }
-
- } else {
- resultRows.push(rowContent);
- if (totals.length > 0) {
- resultRows.push(totals);
- }
-
- }
- } else {
- if (!isColHeader && !isColHeaderDone) {
- tableContent += "</thead><tbody>";
- isColHeaderDone = true;
- }
- tableContent += rowContent;
- if (totals.length > 0) {
- tableContent += totals;
- }
- }
- }
- if (options) {
- options['batchResult'] = resultRows;
- options['hasBatchResult'] = resultRows.length > 0;
- }
- return "<table>" + tableContent + "</tbody></table>";
- };
-
- SaikuTableRenderer.prototype.renderSummary = function(data) {
- if (data && data.query) {
- var hasSomethingToRender = false;
- var measures = data.query.queryModel.details
- ? data.query.queryModel.details.measures
- : [];
- var summaryData = {};
-
- for (var i = 0; i < measures.length; i++) {
- var m = measures[i];
- if (m.aggregators) {
- for (var j = 0; j < m.aggregators.length; j++) {
- var a = m.aggregators[j];
- if (a.indexOf('_') > 0) {
- var tokens = a.split('_');
- var aggregator = tokens[0];
- var axis = tokens[1];
-
- if (aggregator !== 'nil' && aggregator !== 'not') {
- hasSomethingToRender = true;
- aggregator = aggregator.capitalizeFirstLetter();
- if (!(axis in summaryData)) summaryData[axis] = [];
- summaryData[axis].push(m.name + ": " + aggregator);
- }
- }
- }
- }
- }
-
- if (hasSomethingToRender) {
- var summary = "<div id='totals_summary'><br/>";
-
- $.each(summaryData, function(key, aggregators) {
- summary += "<h3>" + key.capitalizeFirstLetter();
- for (var i = 0; i < aggregators.length; i++) {
- summary += "<br/> " + aggregators[i];
- }
- summary += "</h3>";
- });
-
- return summary + "</div>";
- }
- }
-
- return "";
- };
-
- String.prototype.capitalizeFirstLetter = function() {
- return this.charAt(0).toUpperCase() + this.slice(1).toLowerCase();
- }
ExcelWorksheetBuilder.java
- package org.saiku.service.util.export.excel;
-
- import org.apache.commons.lang.StringUtils;
- import org.apache.poi.hssf.usermodel.HSSFPalette;
- import org.apache.poi.hssf.usermodel.HSSFWorkbook;
- import org.apache.poi.ss.SpreadsheetVersion;
- import org.apache.poi.ss.usermodel.*;
- import org.apache.poi.ss.usermodel.Font;
- import org.apache.poi.ss.util.CellRangeAddress;
- import org.apache.poi.xssf.usermodel.XSSFCellStyle;
- import org.apache.poi.xssf.usermodel.XSSFColor;
- import org.apache.poi.xssf.usermodel.XSSFWorkbook;
- import org.olap4j.metadata.Measure;
- import org.saiku.olap.dto.resultset.AbstractBaseCell;
- import org.saiku.olap.dto.resultset.CellDataSet;
- import org.saiku.olap.dto.resultset.DataCell;
- import org.saiku.olap.dto.resultset.MemberCell;
- import org.saiku.olap.query2.ThinHierarchy;
- import org.saiku.olap.query2.ThinLevel;
- import org.saiku.olap.query2.ThinMember;
- import org.saiku.olap.util.SaikuProperties;
- import org.saiku.service.olap.totals.TotalNode;
- import org.saiku.service.olap.totals.aggregators.TotalAggregator;
- import org.saiku.service.util.exception.SaikuServiceException;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
-
- import java.io.ByteArrayOutputStream;
- import java.io.IOException;
- import java.io.InputStream;
- import java.text.SimpleDateFormat;
- import java.util.*;
- import java.util.List;
-
- /**
- * Created with IntelliJ IDEA. User: sramazzina Date: 21/06/12 Time: 7.35 To
- * change this template use File | Settings | File Templates.
- */
-
- public class ExcelWorksheetBuilder {
-
- private static final String BASIC_SHEET_FONT_FAMILY = "Arial";
- private static final short BASIC_SHEET_FONT_SIZE = 11;
- private static final String EMPTY_STRING = "";
- private static final String CSS_COLORS_CODE_PROPERTIES = "css-colors-codes.properties";
-
- private int maxRows = -1;
- private int maxColumns = -1;
-
- private AbstractBaseCell[][] rowsetHeader;
- private AbstractBaseCell[][] rowsetBody;
-
- private Map<Integer, TotalAggregator[][]> rowScanTotals;
- private Map<Integer, TotalAggregator[][]> colScanTotals;
-
- private CellDataSet table;
- private Workbook excelWorkbook;
- private Sheet workbookSheet;
- private String sheetName;
- private int topLeftCornerWidth;
- private int topLeftCornerHeight;
- private CellStyle basicCS;
- private CellStyle totalsCS;
- private CellStyle numberCS;
- private CellStyle number0CS; //use this cellstyle when cell value is 0
- private CellStyle lighterHeaderCellCS;
- private List<ThinHierarchy> queryFilters;
- private Map<String, Integer> colorCodesMap;
-
- private int nextAvailableColorCode = 41;
- private Properties cssColorCodesProperties;
-
- private HSSFPalette customColorsPalette;
- private ExcelBuilderOptions options;
-
- private static final Logger log = LoggerFactory.getLogger(ExcelWorksheetBuilder.class);
-
- public ExcelWorksheetBuilder(CellDataSet table, List<ThinHierarchy> filters, ExcelBuilderOptions options) {
- init(table, filters, options);
- }
-
- private void init(CellDataSet table, List<ThinHierarchy> filters, ExcelBuilderOptions options) {
- this.table = table;
- this.options = options;
- queryFilters = filters;
- maxRows = SpreadsheetVersion.EXCEL2007.getMaxRows();
- maxColumns = SpreadsheetVersion.EXCEL2007.getMaxColumns();
-
- if ("xls".equals(SaikuProperties.webExportExcelFormat)) {
- HSSFWorkbook wb = new HSSFWorkbook();
- customColorsPalette = wb.getCustomPalette();
- excelWorkbook = wb;
- maxRows = SpreadsheetVersion.EXCEL97.getMaxRows();
- maxColumns = SpreadsheetVersion.EXCEL97.getMaxColumns();
- } else if ("xlsx".equals(SaikuProperties.webExportExcelFormat)) {
- excelWorkbook = new XSSFWorkbook();
- } else {
- excelWorkbook = new XSSFWorkbook();
- }
-
- colorCodesMap = new HashMap<>();
- this.sheetName = options.sheetName;
- rowsetHeader = table.getCellSetHeaders();
- rowsetBody = table.getCellSetBody();
-
- topLeftCornerWidth = findTopLeftCornerWidth();
- topLeftCornerHeight = findTopLeftCornerHeight();
-
- initCellStyles();
-
- // Row totals and subtotals
- rowScanTotals = new HashMap<>();
- colScanTotals = new HashMap<>();
- scanRowAndColumnAggregations(table.getRowTotalsLists(), rowScanTotals, table.getColTotalsLists(), colScanTotals);
- }
-
- private void initCellStyles() {
-
- Font font = excelWorkbook.createFont();
- font.setFontHeightInPoints((short) BASIC_SHEET_FONT_SIZE);
- font.setFontName(BASIC_SHEET_FONT_FAMILY);
-
- basicCS = excelWorkbook.createCellStyle();
- basicCS.setFont(font);
- basicCS.setAlignment(HorizontalAlignment.LEFT);
- basicCS.setVerticalAlignment(VerticalAlignment.TOP);
- setCellBordersColor(basicCS);
-
- Font totalsFont = excelWorkbook.createFont();
- totalsFont.setFontHeightInPoints((short) BASIC_SHEET_FONT_SIZE);
- totalsFont.setBold(true);
- totalsFont.setFontName(BASIC_SHEET_FONT_FAMILY);
-
- totalsCS = excelWorkbook.createCellStyle();
- totalsCS.setFont(totalsFont);
- totalsCS.setAlignment(HorizontalAlignment.RIGHT);
- setCellBordersColor(totalsCS);
-
- // Setting the default styling for number cells
- numberCS = excelWorkbook.createCellStyle();
- numberCS.setFont(font);
- numberCS.setAlignment(HorizontalAlignment.RIGHT);
-
- /*
- * justasg: Let's set default format, used if measure has no format at
- * all. More info:
- * http://poi.apache.org/apidocs/org/apache/poi/ss/usermodel/
- * BuiltinFormats.html#getBuiltinFormat(int) If we don't have default
- * format, it will output values up to maximum detail, i.e.
- * 121212.3456789 and we like them as 121,212.346
- */
- DataFormat fmt = excelWorkbook.createDataFormat();
- short dataFormat = fmt.getFormat(SaikuProperties.webExportExcelDefaultNumberFormat);
- numberCS.setDataFormat(dataFormat);
- setCellBordersColor(numberCS);
-
- // Setting the default format for cell value is 0
- number0CS = excelWorkbook.createCellStyle();
- number0CS.setFont(font);
- number0CS.setAlignment(HorizontalAlignment.RIGHT);
- setCellBordersColor(number0CS);
-
- Font headerFont = excelWorkbook.createFont();
- headerFont.setFontHeightInPoints((short) BASIC_SHEET_FONT_SIZE);
- headerFont.setFontName(BASIC_SHEET_FONT_FAMILY);
- headerFont.setBold(true);
-
- lighterHeaderCellCS = excelWorkbook.createCellStyle();
- lighterHeaderCellCS.setFont(headerFont);
- lighterHeaderCellCS.setAlignment(HorizontalAlignment.CENTER);
- lighterHeaderCellCS.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
- lighterHeaderCellCS.setFillPattern(FillPatternType.SOLID_FOREGROUND);
- setCellBordersColor(lighterHeaderCellCS);
-
- CellStyle darkerHeaderCellCS = excelWorkbook.createCellStyle();
- darkerHeaderCellCS.setFont(headerFont);
- darkerHeaderCellCS.setAlignment(HorizontalAlignment.CENTER);
- darkerHeaderCellCS.setFillForegroundColor(IndexedColors.GREY_40_PERCENT.getIndex());
- darkerHeaderCellCS.setFillPattern(FillPatternType.SOLID_FOREGROUND);
- setCellBordersColor(darkerHeaderCellCS);
-
- }
-
- //更改指标分割线条颜色为 黑色20190528
- private void setCellBordersColor(CellStyle style) {
-
- style.setBorderBottom(BorderStyle.THIN);
- style.setBottomBorderColor(IndexedColors.BLACK.getIndex());
- style.setBorderTop(BorderStyle.THIN);
- style.setTopBorderColor(IndexedColors.BLACK.getIndex());
- style.setBorderLeft(BorderStyle.THIN);
- style.setLeftBorderColor(IndexedColors.BLACK.getIndex());
- style.setBorderRight(BorderStyle.THIN);
- style.setRightBorderColor(IndexedColors.BLACK.getIndex());
- /*style.setBorderBottom(BorderStyle.THIN);
- style.setBottomBorderColor(IndexedColors.GREY_80_PERCENT.getIndex());
- style.setBorderTop(BorderStyle.THIN);
- style.setTopBorderColor(IndexedColors.GREY_80_PERCENT.getIndex());
- style.setBorderLeft(BorderStyle.THIN);
- style.setLeftBorderColor(IndexedColors.GREY_80_PERCENT.getIndex());
- style.setBorderRight(BorderStyle.THIN);
- style.setRightBorderColor(IndexedColors.GREY_80_PERCENT.getIndex());*/
- }
-
- public byte[] build() throws SaikuServiceException {
-
- Long start = (new Date()).getTime();
- int startRow = initExcelSheet();
- Long init = (new Date()).getTime();
- int lastHeaderRow = buildExcelTableHeader(startRow);
- Long header = (new Date()).getTime();
- addExcelTableRows(lastHeaderRow);
- addTotalsSummary(lastHeaderRow);
- Long content = (new Date()).getTime();
- finalizeExcelSheet(startRow);
- Long finalizing = (new Date()).getTime();
-
- log.debug("Init: " + (init - start) + "ms header: " + (header - init) + "ms content: " + (content - header)
- + "ms finalizing: " + (finalizing - content) + "ms ");
- ByteArrayOutputStream bout = new ByteArrayOutputStream();
-
- try {
- excelWorkbook.write(bout);
- } catch (IOException e) {
- throw new SaikuServiceException("Error creating excel export for query", e);
- }
- return bout.toByteArray();
- }
-
- private void checkRowLimit(int rowIndex) {
- if ((rowIndex + 1) > maxRows) {
- log.warn("Excel sheet is truncated, only outputting " + maxRows + " rows of " + (rowIndex + 1));
- }
- }
-
- private void addTotalsSummary(int startingRow) {
- int rowIndex = startingRow + rowsetBody.length + 2; // Lines offset after data, in order to add summary
- checkRowLimit(rowIndex);
-
- // Columns summary
- if (colScanTotals.keySet().size() > 0) {
- Row row = workbookSheet.createRow(rowIndex);
- Cell cell = row.createCell(0);
- cell.setCellStyle(lighterHeaderCellCS);
- cell.setCellValue("Columns");
-
- for (Integer colKey : colScanTotals.keySet()) {
- TotalAggregator[][] colAggregator = colScanTotals.get(colKey);
-
- if (colAggregator == null) continue;
-
- for (int x = 0; x < colAggregator.length; x++) {
- rowIndex++;
- checkRowLimit(rowIndex);
-
- Measure measure = this.table.getSelectedMeasures()[x];
-
- TotalAggregator agg = colAggregator[x][0];
- row = workbookSheet.createRow(rowIndex);
-
- // Measure name
- cell = row.createCell(0);
- cell.setCellStyle(lighterHeaderCellCS);
- cell.setCellValue(measure.getCaption() + ":");
-
- // Measure aggregator
- cell = row.createCell(1);
- cell.setCellStyle(basicCS);
- cell.setCellValue(agg.getClass().getSimpleName().substring(0, 3));
- }
- }
- }
-
- // Rows summary
- if (rowScanTotals.keySet().size() > 0) {
- rowIndex++;
- checkRowLimit(rowIndex);
-
- Row row = workbookSheet.createRow(rowIndex);
- Cell cell = row.createCell(0);
- cell.setCellStyle(lighterHeaderCellCS);
- cell.setCellValue("Rows");
-
- for (Integer rowKey : rowScanTotals.keySet()) {
- TotalAggregator[][] rowAggregator = rowScanTotals.get(rowKey);
-
- if (rowAggregator == null) continue;
-
- for (int x = 0; x < rowAggregator.length; x++) {
- for (int y = 0; y < this.table.getSelectedMeasures().length; y++) {
- rowIndex++;
- checkRowLimit(rowIndex);
-
- Measure measure = this.table.getSelectedMeasures()[y];
- TotalAggregator agg = rowAggregator[x][y];
-
- row = workbookSheet.createRow(rowIndex);
-
- // Measure name
- cell = row.createCell(0);
- cell.setCellStyle(lighterHeaderCellCS);
- cell.setCellValue(measure.getCaption() + ":");
-
- // Measure aggregator
- cell = row.createCell(1);
- cell.setCellStyle(basicCS);
- cell.setCellValue(agg.getClass().getSimpleName().substring(0, 3));
- }
- }
- }
- }
-
- }
-
- private void finalizeExcelSheet(int startRow) {
-
- boolean autoSize = (rowsetBody != null && rowsetBody.length > 0 && rowsetBody.length < 10000
- && rowsetHeader != null && rowsetHeader.length > 0 && rowsetHeader[0].length < 200);
-
- if (autoSize) {
- log.warn("Skipping auto-sizing columns, more than 10000 rows and/or 200 columns");
- }
-
- Long start = (new Date()).getTime();
- if (autoSize) {
- //Detect max column size
- int maxColumnsSize = rowsetBody[0].length;
- if (!colScanTotals.isEmpty()) {
- maxColumnsSize = Collections.max(colScanTotals.keySet()) + 1;
- }
- // Autosize columns
- for (int i = 0; i < maxColumns && i < maxColumnsSize; i++) {
- workbookSheet.autoSizeColumn(i);
- }
- }
- Long end = (new Date()).getTime();
- log.debug("Autosizing: " + (end - start) + "ms");
- // Freeze the header columns
- int headerWidth = rowsetHeader.length;
- workbookSheet.createFreezePane(0, startRow + headerWidth, 0, startRow + headerWidth);
- }
-
- private int initExcelSheet() {
- // Main Workbook Sheet
- if (StringUtils.isNotBlank(options.sheetName)) {
- workbookSheet = excelWorkbook.createSheet(this.sheetName);
- } else {
- workbookSheet = excelWorkbook.createSheet();
- }
- initSummarySheet();
- return 0;
- }
-
- private void initSummarySheet() {
-
- // Main Workbook Sheet
- Sheet summarySheet = excelWorkbook.createSheet("Summary page");
-
- int row = 1;
-
- Row sheetRow = summarySheet.createRow((int) row);
- Cell cell = sheetRow.createCell(0);
- String todayDate = (new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")).format(new Date());
- cell.setCellValue("Export date and time: " + todayDate);
- summarySheet.addMergedRegion(new CellRangeAddress(1, 1, 0, 2));
- row = row + 2;
-
- sheetRow = summarySheet.createRow((int) row);
- cell = sheetRow.createCell(0);
- cell.setCellValue("Dimension");
- cell = sheetRow.createCell(1);
- cell.setCellValue("Level");
- cell = sheetRow.createCell(2);
- cell.setCellValue("Filter Applied");
- row++;
-
- if (queryFilters != null) {
- for (ThinHierarchy item : queryFilters) {
- for (ThinLevel s : item.getLevels().values()) {
- for (ThinMember i : s.getSelection().getMembers()) {
- sheetRow = summarySheet.createRow((short) row);
- cell = sheetRow.createCell(0);
- cell.setCellValue(item.getCaption());
- cell = sheetRow.createCell(1);
- cell.setCellValue(s.getCaption());
- cell = sheetRow.createCell(2);
- cell.setCellValue(i.getCaption());
- row++;
- }
- }
- }
- }
-
- row += 2;
-
- int rowLength = (rowsetBody != null) ? rowsetBody.length : 0;
- int columnCount = (rowsetHeader != null && rowsetHeader.length > 0) ? rowsetHeader[0].length : 0;
- int headerLength = (rowsetHeader != null) ? rowsetHeader.length : 0;
-
- if (columnCount > maxColumns) {
- sheetRow = summarySheet.createRow((int) row);
- cell = sheetRow.createCell(0);
- cell.setCellValue("Excel sheet is truncated, only contains " + maxColumns + " columns of " + (columnCount));
- summarySheet.addMergedRegion(new CellRangeAddress(row, row, 0, 10));
- row++;
- }
-
- if ((headerLength + rowLength) > maxRows) {
- sheetRow = summarySheet.createRow((int) row);
- cell = sheetRow.createCell(0);
- cell.setCellValue(
- "Excel sheet is truncated, only contains " + maxRows + " rows of " + (headerLength + rowLength));
- summarySheet.addMergedRegion(new CellRangeAddress(row, row, 0, 10));
- row++;
- }
-
- row++;
-
- sheetRow = summarySheet.createRow((int) row);
- cell = sheetRow.createCell(0);
- cell.setCellValue(SaikuProperties.webExportExcelPoweredBy);
- summarySheet.addMergedRegion(new CellRangeAddress(row, row, 0, 10));
-
- // Autosize columns for summary sheet
- for (int i = 0; i < 5; i++) {
- summarySheet.autoSizeColumn(i);
- }
- }
-
- private void addExcelTableRows(int startingRow) {
-
- Row sheetRow = null;
- Cell cell = null;
- Map<Integer, String> tmpCellUniqueValueByColumn = new HashMap<>();
- Map<Integer, Map<Integer, Boolean>> mergeRowsByColumn = new HashMap<>();
-
- if ((startingRow + rowsetBody.length) > maxRows) {
- log.warn("Excel sheet is truncated, only outputting " + maxRows + " rows of "
- + (rowsetBody.length + startingRow));
- }
- if (rowsetBody.length > 0 && rowsetBody[0].length > maxColumns) {
- log.warn("Excel sheet is truncated, only outputting " + maxColumns + " columns of "
- + (rowsetBody[0].length));
- }
-
- int rowCount = startingRow;
-
- for (int x = 0; (x + startingRow) < maxRows && x < rowsetBody.length; x++) {
-
- int excelRowIndex = x + startingRow;
- sheetRow = workbookSheet.createRow(excelRowIndex);
-
- int column = 0;
- for (int y = 0; y < maxColumns && y < rowsetBody[x].length; y++) {
- cell = sheetRow.createCell(column);
-
- AbstractBaseCell baseCell = rowsetBody[x][y];
-
- //Detect merge cells
- findMergeCells(baseCell, excelRowIndex, y, mergeRowsByColumn, tmpCellUniqueValueByColumn);
-
- String value = baseCell.getFormattedValue();
-
- if (value == null && options.repeatValues) {
- // If the row cells has a null values it means the value is
- // repeated in the data internally
- // but not in the interface. To properly format the Excel
- // export file we need that value so we
- // get it from the same position in the prev row
- value = workbookSheet.getRow(sheetRow.getRowNum() - 1).getCell(column).getStringCellValue();
- }
-
- cell.setCellStyle(basicCS);
- cell.setCellValue(value);
- // Use rawNumber only is there is a formatString
- if (rowsetBody[x][y] instanceof DataCell) {
- DataCell dataCell = (DataCell) rowsetBody[x][y];
- String formatString = dataCell.getFormatString();
- if ((dataCell.getRawNumber() != null) && (formatString != null) && !formatString.trim().isEmpty()) {
- Number numberValue = dataCell.getRawNumber();
- cell.setCellValue(numberValue.doubleValue());
- if(numberValue.doubleValue() != 0.0 || !formatString.trim().equals("#,###")){//不等于0.0时 就设置样式
- applyCellFormatting(cell, dataCell);
- }else{
- cell.setCellStyle(number0CS);
- }
- }
- }
-
- //Set column sub totalstotals
- column = setColTotalAggregationCell(colScanTotals, sheetRow, x, column, true, false);
-
- //Set column grand totals
- if (y == rowsetBody[x].length - 1) {
- setColTotalAggregationCell(colScanTotals, sheetRow, x, column - 1, true, x == 0);
- }
- }
-
- // Set row sub totals
- startingRow = setRowTotalAggregationCell(rowScanTotals, startingRow, x, false);
- rowCount = startingRow + x;
- }
-
- //Set row grand totals
- setRowTotalAggregationCell(rowScanTotals, rowCount, 0, true);
-
- //Add merge cells
- addMergedRegions(mergeRowsByColumn);
- }
-
- private void scanRowAndColumnAggregations(List<TotalNode>[] rowTotalsLists, Map<Integer, TotalAggregator[][]> rowScanTotals, List<TotalNode>[] colTotalsLists, Map<Integer, TotalAggregator[][]> colScanTotals) {
- if (rowTotalsLists != null) {
- for (List<TotalNode> totalNodes : rowTotalsLists) {
- //Scan row totals
- scanAggregations(true, totalNodes, rowScanTotals);
- }
- }
- if (colTotalsLists != null) {
- for (List<TotalNode> totalNodes : colTotalsLists) {
- //Scan Columns grand totals
- scanAggregations(false, totalNodes, colScanTotals);
- }
- }
- }
-
- private void scanAggregations(boolean row, List<TotalNode> totalNodes, Map<Integer, TotalAggregator[][]> scanSums) {
- if (totalNodes != null && (!totalNodes.isEmpty())) {
- int index;
- if (row) {
- index = rowsetHeader.length - 2;
- } else {
- index = detectColumnStartIndex();
- }
- for (TotalNode n : totalNodes) {
- TotalAggregator[][] tg = n.getTotalGroups();
- if (tg.length > 0) {
- if (n.getSpan() > n.getWidth()) {
- index += n.getSpan();
- } else {
- index += n.getWidth();
- }
- index++;
- scanSums.put(index, tg);
- }
- }
- }
- }
-
- private int setRowTotalAggregationCell(Map<Integer, TotalAggregator[][]> scanTotals, int startIndex, int subIndex, boolean grandTotal) {
- if (!scanTotals.isEmpty()) {
- int row = subIndex + startIndex;
- TotalAggregator[][] aggregatorsTable = scanTotals.get(row);
- if (aggregatorsTable != null) {
- //Create totals row
- Row sheetRow = workbookSheet.createRow(row + 1);
-
- //Detect column start index
- int startColumnIndex = detectColumnStartIndex();
-
- if (grandTotal) {
- setGrandTotalLabel(sheetRow, startColumnIndex, false);
- }
-
- for (TotalAggregator[] aggregators : aggregatorsTable) {
-
- int column = startColumnIndex;
-
- for (TotalAggregator aggregator : aggregators) {
-
- //Calculate column sub total index
- column = setColTotalAggregationCell(colScanTotals, null, -1, column, false, false);
-
- //Create row totals cell
- Cell cell = sheetRow.createCell(column);
- String value = aggregator.getFormattedValue();
- cell.setCellValue(value);
- cell.setCellStyle(totalsCS);
- }
- }
- startIndex++;
- }
- }
- return startIndex;
- }
-
- /**
- * @return columns data start index
- */
- private int detectColumnStartIndex() {
- int index = 0;
- if (rowsetBody.length > 0) {
- for (AbstractBaseCell cell : rowsetBody[0]) {
- if (cell instanceof MemberCell) {
- index++;
- }
- }
- index--;
- }
- return index;
- }
-
- private int setColTotalAggregationCell(Map<Integer, TotalAggregator[][]> scanTotals, Row sheetRow, int x, int column, boolean setValue, boolean grandTotal) {
- column++;
-
- if (!scanTotals.isEmpty()) {
- TotalAggregator[][] aggregatorsTable = scanTotals.get(column);
-
- if (aggregatorsTable != null) {
- if (setValue) {
- if (grandTotal) {
- setGrandTotalLabel(sheetRow.getRowNum() - 1, column, true);
- }
-
- for (TotalAggregator[] aggregators : aggregatorsTable) {
- Cell cell = sheetRow.createCell(column);
- String value = aggregators[x].getFormattedValue();
- cell.setCellValue(value);
- cell.setCellStyle(totalsCS);
- column++;
- }
- }
- }
- }
-
- return column;
- }
-
- private void setGrandTotalLabel(int x, int y, boolean header) {
- Row sheetRow = workbookSheet.getRow(x);
- if (sheetRow != null) {
- setGrandTotalLabel(sheetRow, y, header);
- }
- }
-
- private void setGrandTotalLabel(Row sheetRow, int y, boolean header) {
- Cell cell = sheetRow.createCell(y);
- //TODO i18n
- String value = "Grand Total";
- if (header) {
- fillHeaderCell(sheetRow, value, y);
- } else {
- cell.setCellValue(value);
- cell.setCellStyle(basicCS);
- }
- }
-
- /**
- * Apply exact number format to excel Cell from its DataCell. Caller checks
- * the DataCell rawNumber and formatString are correct.
- *
- * @param cell The excel cell to apply formatting
- * @param dataCell The source
- */
- private void applyCellFormatting(Cell cell, DataCell dataCell) {
- /*
- * Previously, the CellStyles were being kept on a hash map for reuse,
- * but the key used was just the formatString (not considering the
- * colours), so, if many cells shared the same formatString but using
- * different colours, all those cells would have the last cell colour.
- */
- String formatString = dataCell.getFormatString();
- CellStyle numberCSClone = excelWorkbook.createCellStyle();
-
- numberCSClone.cloneStyleFrom(numberCS);
-
- try {
- formatString = FormatUtil.getFormatString(formatString);
- DataFormat fmt = excelWorkbook.createDataFormat();
- short dataFormat = fmt.getFormat(formatString);
- numberCSClone.setDataFormat(dataFormat);
- } catch (Exception ex) {
-
- }
-
- // Check for cell background
- Map<String, String> properties = dataCell.getProperties();
-
- // Just style the cell if it contains a value
- if (dataCell.getRawNumber() != null && properties.containsKey("style")) {
- String colorCode = properties.get("style");
- short colorCodeIndex = getColorFromCustomPalette(colorCode);
-
- if (colorCodeIndex != -1) {
- numberCSClone.setFillForegroundColor(colorCodeIndex);
- numberCSClone.setFillPattern(FillPatternType.SOLID_FOREGROUND);
- } else if (customColorsPalette == null) {
- try {
-
- if (cssColorCodesProperties != null && cssColorCodesProperties.containsKey(colorCode)) {
- colorCode = cssColorCodesProperties.getProperty(colorCode);
- }
-
- int redCode = Integer.parseInt(colorCode.substring(1, 3), 16);
- int greenCode = Integer.parseInt(colorCode.substring(3, 5), 16);
- int blueCode = Integer.parseInt(colorCode.substring(5, 7), 16);
-
- numberCSClone.setFillPattern(FillPatternType.SOLID_FOREGROUND);
-
- ((XSSFCellStyle) numberCSClone).setFillForegroundColor(
- new XSSFColor(new java.awt.Color(redCode, greenCode, blueCode)));
- ((XSSFCellStyle) numberCSClone).setFillBackgroundColor(
- new XSSFColor(new java.awt.Color(redCode, greenCode, blueCode)));
- } catch (Exception e) {
- // we tried to set the color, no luck, lets continue
- // without
- }
-
- }
- } else {
- numberCSClone.setFillForegroundColor(numberCS.getFillForegroundColor());
- numberCSClone.setFillBackgroundColor(numberCS.getFillBackgroundColor());
- }
-
- cell.setCellStyle(numberCSClone);
- }
-
- private short getColorFromCustomPalette(String style) {
-
- short returnedColorIndex = -1;
- InputStream is = null;
-
- if (colorCodesMap.containsKey(style)) {
- returnedColorIndex = colorCodesMap.get(style).shortValue();
- } else {
- try {
-
- if (cssColorCodesProperties == null) {
- is = getClass().getResourceAsStream(CSS_COLORS_CODE_PROPERTIES);
- if (is != null) {
- cssColorCodesProperties = new Properties();
- cssColorCodesProperties.load(is);
- }
- }
-
- String colorCode = cssColorCodesProperties.getProperty(style);
- if (colorCode != null) {
- try {
- int redCode = Integer.parseInt(colorCode.substring(1, 3), 16);
- int greenCode = Integer.parseInt(colorCode.substring(3, 5), 16);
- int blueCode = Integer.parseInt(colorCode.substring(5, 7), 16);
- if (customColorsPalette != null) {
- customColorsPalette.setColorAtIndex((byte) nextAvailableColorCode, (byte) redCode,
- (byte) greenCode, (byte) blueCode);
- returnedColorIndex = customColorsPalette.getColor(nextAvailableColorCode).getIndex();
- colorCodesMap.put(style, (int) returnedColorIndex);
- } else {
- return -1;
- }
- nextAvailableColorCode++;
- } catch (Exception e) {
- // we tried to set the color, no luck, lets continue
- // without
- return -1;
- }
- }
- } catch (IOException e) {
- log.error("IO Exception", e);
- } finally {
- try {
- if (is != null)
- is.close();
- } catch (IOException e) {
- log.error("IO Exception", e);
- }
- }
-
- }
-
- return returnedColorIndex; // To change body of created methods use File
- // | Settings | File Templates.
- }
-
- private int buildExcelTableHeader(int startRow) {
-
- Row sheetRow = null;
- int x = 0;
- int y = 0;
- int startSameFromPos = 0;
- int mergedCellsWidth = 0;
- boolean isLastHeaderRow = false;
- boolean isLastColumn = false;
- String nextHeader = EMPTY_STRING;
- String currentHeader = EMPTY_STRING;
- ArrayList<ExcelMergedRegionItemConfig> mergedItemsConfig = new ArrayList<>();
-
- for (x = 0; x < rowsetHeader.length; x++) {
-
- sheetRow = workbookSheet.createRow((int) x + startRow);
-
- nextHeader = EMPTY_STRING;
- isLastColumn = false;
- startSameFromPos = 0;
- mergedCellsWidth = 0;
-
- if (x + 1 == rowsetHeader.length) {
- isLastHeaderRow = true;
- }
-
- int column = 0;
- for (y = 0; y < maxColumns && y < rowsetHeader[x].length; y++) {
-
- currentHeader = rowsetHeader[x][y].getFormattedValue();
- if (currentHeader != null) {
- if (rowsetHeader[x].length == y + 1) {
- isLastColumn = true;
- } else {
- nextHeader = rowsetHeader[x][y + 1].getFormattedValue();
- }
-
- manageColumnHeaderDisplay(sheetRow, x, column, currentHeader);
-
- if (!isLastHeaderRow) {
- if (nextHeader != null && !nextHeader.equals(currentHeader) || isLastColumn) {
- manageCellsMerge(column, x + startRow, mergedCellsWidth + 1, startSameFromPos,
- mergedItemsConfig);
- startSameFromPos = column + 1;
- mergedCellsWidth = 0;
- } else if (nextHeader != null && nextHeader.equals(currentHeader)) {
- mergedCellsWidth++;
- }
- }
- } else {
- startSameFromPos++;
- }
-
- //Set sub total column space
- int nextColumn = setColTotalAggregationCell(colScanTotals, sheetRow, x, column, false, false);
- if (column != nextColumn - 1) {
- startSameFromPos++;
- }
- column = nextColumn;
- }
- // Manage the merge condition on exit from columns scan
- if (!isLastHeaderRow)
- manageCellsMerge(y - 1, x, mergedCellsWidth + 1, startSameFromPos, mergedItemsConfig);
- }
-
- if (topLeftCornerHeight > 0 && topLeftCornerWidth > 0) {
- workbookSheet.addMergedRegion(
- new CellRangeAddress(startRow, startRow + topLeftCornerHeight - 1, 0, topLeftCornerWidth - 1));
- }
-
- if (mergedItemsConfig.size() > 0) {
- for (ExcelMergedRegionItemConfig item : mergedItemsConfig) {
- int lastCol = item.getStartX() + item.getWidth() - 1;
- lastCol = lastCol >= maxColumns ? maxColumns - 1 : lastCol;
- workbookSheet.addMergedRegion(new CellRangeAddress(item.getStartY(),
- item.getStartY() + item.getHeight(), item.getStartX(), lastCol));
- }
- }
-
- return x + startRow;
- }
-
- private void manageColumnHeaderDisplay(Row sheetRow, int x, int y, String currentHeader) {
- if (topLeftCornerHeight > 0 && x >= topLeftCornerHeight) {
- fillHeaderCell(sheetRow, currentHeader, y);
- } else if ((topLeftCornerHeight > 0 && x < topLeftCornerHeight)
- && (topLeftCornerWidth > 0 && y >= topLeftCornerWidth)) {
- fillHeaderCell(sheetRow, currentHeader, y);
- } else if (topLeftCornerHeight == 0 && topLeftCornerWidth == 0)
- fillHeaderCell(sheetRow, currentHeader, y);
- }
-
- private void manageCellsMerge(int rowPos, int colPos, int width, int startSameFromPos,
- ArrayList<ExcelMergedRegionItemConfig> mergedItemsConfig) {
-
- ExcelMergedRegionItemConfig foundItem = null;
- boolean itemGetFromList = false;
-
- if (width == 1)
- return;
-
- for (ExcelMergedRegionItemConfig item : mergedItemsConfig) {
- if (item.getStartY() == colPos && item.getStartX() == rowPos) {
- foundItem = item;
- itemGetFromList = true;
- }
- }
-
- if (foundItem == null)
- foundItem = new ExcelMergedRegionItemConfig();
-
- foundItem.setHeight(0);
- foundItem.setWidth(width);
- foundItem.setStartX(startSameFromPos);
- foundItem.setStartY(colPos);
- if (mergedItemsConfig.isEmpty() || !itemGetFromList)
- mergedItemsConfig.add(foundItem);
- }
-
- private void fillHeaderCell(Row sheetRow, String formattedValue, int y) {
- Cell cell = sheetRow.createCell(y);
- cell.setCellValue(formattedValue);
- cell.setCellStyle(lighterHeaderCellCS);
- }
-
- /**
- * Find the width in cells of the top left corner of the table
- *
- * @return
- */
- private int findTopLeftCornerWidth() {
-
- int width = 0;
- int x = 0;
- boolean exit = (rowsetHeader.length < 1 || rowsetHeader[0][0].getRawValue() != null);
- String cellValue = null;
-
- for (x = 0; (!exit && rowsetHeader[0].length > x); x++) {
-
- cellValue = rowsetHeader[0][x].getRawValue();
- if (cellValue == null) {
- width = x + 1;
- } else {
- exit = true;
- }
- }
-
- return width;
- }
-
- /**
- * Find the height in cells of the top left corner of the table
- *
- * @return
- */
- private int findTopLeftCornerHeight() {
- return rowsetHeader.length > 0 ? rowsetHeader.length - 1 : 0;
- }
-
- /**
- * @param mergeRowsByColumn merged indexes
- */
- private void addMergedRegions(Map<Integer, Map<Integer, Boolean>> mergeRowsByColumn) {
- if (mergeRowsByColumn != null) {
- for (Map.Entry<Integer, Map<Integer, Boolean>> e : mergeRowsByColumn.entrySet()) {
-
- int col = e.getKey();
-
- Map<Integer, Boolean> rows = e.getValue();
-
- if (rows != null) {
-
- int mergeCount = 1;
- for (Map.Entry<Integer, Boolean> rowEntry : rows.entrySet()) {
-
- int row = rowEntry.getKey();
-
- boolean current = rowEntry.getValue();
-
- Boolean next = rows.get(rowEntry.getKey() + 1);
-
- if (current) {
- if (next == null || !next) {
- workbookSheet.addMergedRegion(new CellRangeAddress(row - mergeCount, row, col, col));
- }
- mergeCount++;
- } else {
- mergeCount = 1;
- }
- }
- }
- }
- }
- }
-
- /**
- * @param baseCell current cell
- * @param excelRowIndex row index
- * @param y column
- * @param mergeRowsByColumn merge indexes store
- * @param tmpCellUniqueValueByColumn tmp map to compare previews value(max possible value = columns size)
- */
- private void findMergeCells(AbstractBaseCell baseCell,
- int excelRowIndex,
- int y,
- Map<Integer, Map<Integer, Boolean>> mergeRowsByColumn,
- Map<Integer, String> tmpCellUniqueValueByColumn) {
- if (baseCell instanceof MemberCell) {
-
- MemberCell memberCell = (MemberCell) baseCell;
-
- Map<Integer, Boolean> rowMerge = mergeRowsByColumn.get(y);
- if (rowMerge == null) {
- rowMerge = new TreeMap<>();
- mergeRowsByColumn.put(y, rowMerge);
- }
-
- //Compare preview and current cells
- String previousValue = tmpCellUniqueValueByColumn.get(y);
-
- Map<Integer, Boolean> previousColumn = mergeRowsByColumn.get(y - 1);
-
- boolean merge = previousValue != null && previousValue.equals(memberCell.getUniqueName());
-
- if (previousColumn != null) {
- Boolean previewColumnCellmergeValue = previousColumn.get(excelRowIndex);
- if ((previewColumnCellmergeValue != null) && (!previewColumnCellmergeValue) && merge) {
- merge = false;
- }
- }
- rowMerge.put(excelRowIndex, merge);
-
- tmpCellUniqueValueByColumn.put(y, memberCell.getUniqueName());
- }
- }
- }
FlattenedCellSetFormatter.java
- /*
- * Copyright 2012 OSBI Ltd
- *
- * 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.
- */
- package org.saiku.olap.util.formatter;
-
- import java.text.DecimalFormat;
- import java.text.DecimalFormatSymbols;
- import java.util.AbstractList;
- import java.util.ArrayList;
- import java.util.Collections;
- import java.util.HashMap;
- import java.util.Iterator;
- import java.util.List;
- import java.util.Map;
-
- import org.olap4j.Cell;
- import org.olap4j.CellSet;
- import org.olap4j.CellSetAxis;
- import org.olap4j.Position;
- import org.olap4j.impl.CoordinateIterator;
- import org.olap4j.impl.Olap4jUtil;
- import org.olap4j.metadata.Level;
- import org.olap4j.metadata.Member;
- import org.olap4j.metadata.Property;
- import org.saiku.olap.dto.resultset.DataCell;
- import org.saiku.olap.dto.resultset.Matrix;
- import org.saiku.olap.dto.resultset.MemberCell;
- import org.saiku.olap.util.SaikuProperties;
-
-
- public class FlattenedCellSetFormatter implements ICellSetFormatter {
-
- /**
- * Description of an axis.
- */
- private static class AxisInfo {
- final List<AxisOrdinalInfo> ordinalInfos;
-
- /**
- * Creates an AxisInfo.
- *
- * @param ordinalCount Number of hierarchies on this axis
- */
- AxisInfo(final int ordinalCount) {
- ordinalInfos = new ArrayList<>(ordinalCount);
-
- // For each index from 0 to the number of hierarchies ...
- for (int i = 0; i < ordinalCount; i++) {
- // Associate an AxisOrdinalInfo instance
- ordinalInfos.add(new AxisOrdinalInfo());
- }
- }
-
- /**
- * Returns the number of matrix columns required by this axis. The sum of
- * the width of the hierarchies on this axis.
- *
- * @return Width of axis
- */
- public int getWidth() {
- int width = 0;
- for (final AxisOrdinalInfo info : ordinalInfos) {
- width += info.getWidth();
- }
- return width;
- }
- }
-
- /**
- * Description of a particular hierarchy mapped to an axis.
- */
- private static class AxisOrdinalInfo {
- private final List<Integer> depths = new ArrayList<>();
- private final Map<Integer,Level> depthLevel = new HashMap<>();
-
- public int getWidth() {
- return depths.size();
- }
-
- public List<Integer> getDepths() {
- return depths;
- }
-
- public Level getLevel(Integer depth) {
- return depthLevel.get(depth);
- }
-
- public void addLevel(Integer depth, Level level) {
- depthLevel.put(depth, level);
- }
- }
-
- /**
- * Returns an iterator over cells in a result.
- */
- private static Iterable<Cell> cellIter(final int[] pageCoords, final CellSet cellSet) {
- return new Iterable<Cell>() {
- public Iterator<Cell> iterator() {
- final int[] axisDimensions = new int[cellSet.getAxes().size() - pageCoords.length];
- assert pageCoords.length <= axisDimensions.length;
- for (int i = 0; i < axisDimensions.length; i++) {
- final CellSetAxis axis = cellSet.getAxes().get(i);
- axisDimensions[i] = axis.getPositions().size();
- }
- final CoordinateIterator coordIter = new CoordinateIterator(axisDimensions, true);
- return new Iterator<Cell>() {
- public boolean hasNext() {
- return coordIter.hasNext();
- }
-
- public Cell next() {
- final int[] ints = coordIter.next();
- final AbstractList<Integer> intList = new AbstractList<Integer>() {
- @Override
- public Integer get(final int index) {
- return index < ints.length ? ints[index] : pageCoords[index - ints.length];
- }
-
- @Override
- public int size() {
- return pageCoords.length + ints.length;
- }
- };
- return cellSet.getCell(intList);
- }
-
- public void remove() {
- throw new UnsupportedOperationException();
- }
- };
- }
- };
- }
-
- private Matrix matrix;
-
- private final List<Integer> ignorex = new ArrayList<>();
- private final List<Integer> ignorey = new ArrayList<>();
-
- /**
- * This is the main method of a cellset formatter, it receives a cellset as
- * input and converts it on a matrix, a bidimensional representation of query
- * values, arranged in a xy cartesian coordinate system.
- * @param cellSet
- * @return
- */
- public Matrix format(final CellSet cellSet) {
- // Compute how many rows are required to display the columns axis.
- final CellSetAxis columnsAxis;
-
- // If the axes are not empty, the first one is the column axis
- if (cellSet.getAxes().size() > 0) {
- // As a convention, the columns axis is associated with the index 0
- columnsAxis = cellSet.getAxes().get(0);
- } else {
- columnsAxis = null;
- }
-
- final AxisInfo columnsAxisInfo = computeAxisInfo(columnsAxis);
-
- // Compute how many columns are required to display the rows axis.
- final CellSetAxis rowsAxis;
-
- // If there are more than one axis, the second one is the rows axis
- if (cellSet.getAxes().size() > 1) {
- // As a convention, the rows axis is associated with the index 1
- rowsAxis = cellSet.getAxes().get(1);
- } else {
- rowsAxis = null;
- }
-
- final AxisInfo rowsAxisInfo = computeAxisInfo(rowsAxis);
-
- if (cellSet.getAxes().size() > 2) {
- final int[] dimensions = new int[cellSet.getAxes().size() - 2];
- for (int i = 2; i < cellSet.getAxes().size(); i++) {
- final CellSetAxis cellSetAxis = cellSet.getAxes().get(i);
- dimensions[i - 2] = cellSetAxis.getPositions().size();
- }
- for (final int[] pageCoords : CoordinateIterator.iterate(dimensions)) {
- matrix = formatPage(cellSet, pageCoords, columnsAxis, columnsAxisInfo, rowsAxis, rowsAxisInfo);
- }
- } else {
- matrix = formatPage(cellSet, new int[] {}, columnsAxis, columnsAxisInfo, rowsAxis, rowsAxisInfo);
- }
-
- return matrix;
- }
-
- /**
- * Computes a description of an axis. Each axis is composed by many positions,
- * each position is then composed by many members. A member is a 'point' on a
- * dimension of a cube. Every member belongs to a Level of a Hierarchy. The
- * member's depth is its distance to the root member.
- *
- * @param axis Axis
- * @return Description of axis
- */
- private AxisInfo computeAxisInfo(final CellSetAxis axis) {
- if (axis == null) {
- return new AxisInfo(0);
- }
-
- // An axis info is created by informing the number of hierarchies of axis
- final AxisInfo axisInfo = new AxisInfo(axis.getAxisMetaData().getHierarchies().size());
- int p = -1;
-
- // For each axis position
- for (final Position position : axis.getPositions()) {
- ++p;
- int k = -1;
-
- // For each member of the axis
- for (final Member member : position.getMembers()) {
- ++k;
-
- // Fetch the AxisOrdinalInfo instance of the position index k
- final AxisOrdinalInfo axisOrdinalInfo = axisInfo.ordinalInfos.get(k);
-
- // We avoid duplicating information for members with the same depth
- if (!axisOrdinalInfo.getDepths().contains(member.getDepth())) {
- axisOrdinalInfo.getDepths().add(member.getDepth());
- // For each depth of the hiearchy, add its level
- axisOrdinalInfo.addLevel(member.getDepth(), member.getLevel());
- Collections.sort(axisOrdinalInfo.depths);
- }
- }
- }
-
- // The axisInfo object, contains a collection of the hiearchy's levels
- // sorted by their depths.
- return axisInfo;
- }
-
- /**
- * Formats a two-dimensional page.
- *
- * @param cellSet
- * Cell set
- * @param pageCoords
- * Print writer
- * @param pageCoords
- * Coordinates of page [page, chapter, section, ...]
- * @param columnsAxis
- * Columns axis
- * @param columnsAxisInfo
- * Description of columns axis
- * @param rowsAxis
- * Rows axis
- * @param rowsAxisInfo
- * Description of rows axis
- */
- private Matrix formatPage(final CellSet cellSet, final int[] pageCoords, final CellSetAxis columnsAxis,
- final AxisInfo columnsAxisInfo, final CellSetAxis rowsAxis, final AxisInfo rowsAxisInfo) {
-
- // Figure out the dimensions of the blank rectangle in the top left
- // corner.
- final int yOffset = columnsAxisInfo.getWidth();
- final int xOffsset = rowsAxisInfo.getWidth();
-
- // Populate a string matrix
- final Matrix matrix = new Matrix(xOffsset + (columnsAxis == null ? 1 : columnsAxis.getPositions().size()),
- yOffset + (rowsAxis == null ? 1 : rowsAxis.getPositions().size()));
-
- // Populate corner
- List<Level> levels = new ArrayList<>();
- if (rowsAxis != null && rowsAxis.getPositions().size() > 0) {
- // We assume that every position contains members with same levels,
- // so, we just need the first position to retrieve this information.
- Position p = rowsAxis.getPositions().get(0);
-
- for (int m = 0; m < p.getMembers().size(); m++) {
- AxisOrdinalInfo a = rowsAxisInfo.ordinalInfos.get(m);
-
- // For each member's depth of the first position, add its level
- for (Integer depth : a.getDepths()) {
- levels.add(a.getLevel(depth));
- }
- }
-
- for (int x = 0; x < xOffsset; x++) {
- Level xLevel = levels.get(x);
- String s = xLevel.getCaption();
- for (int y = 0; y < yOffset; y++) {
- final MemberCell memberInfo = new MemberCell(false, x > 0);
- if (y == yOffset-1) {
- memberInfo.setRawValue(s);
- memberInfo.setFormattedValue(s);
- memberInfo.setProperty("__headertype", "row_header_header");
- memberInfo.setProperty("levelindex", "" + levels.indexOf(xLevel));
- memberInfo.setHierarchy(xLevel.getHierarchy().getUniqueName());
- memberInfo.setParentDimension(xLevel.getDimension().getName());
- memberInfo.setLevel(xLevel.getUniqueName());
- }
- matrix.set(x, y, memberInfo);
- }
-
- }
- }
- // Populate matrix with cells representing axes
- populateAxis(matrix, columnsAxis, columnsAxisInfo, true, xOffsset);
- populateAxis(matrix, rowsAxis, rowsAxisInfo, false, yOffset);
-
- // TODO - why did we do this in the first place??? HERE BE DRAGONS
- // int headerwidth = matrix.getMatrixWidth();
- // if (headerwidth > 2) {
- // for(int yy=matrix.getMatrixHeight(); yy > matrix.getOffset() ; yy--) {
- // for(int xx=0; xx < headerwidth-1;xx++) {
- // if (matrix.get(xx,yy-1) != null && matrix.get(xx,yy) != null && matrix.get(xx,yy-1).getRawValue() != null
- // && matrix.get(xx,yy-1).getRawValue().equals(matrix.get(xx, yy).getRawValue()))
- // {
- // matrix.set(xx, yy, new MemberCell());
- // }
- // else {
- // break;
- // }
- // }
- // }
- // }
-
-
- // Populate cell values
- int newyOffset = yOffset;
- int newxOffset = xOffsset;
- List<Integer> donex = new ArrayList<>();
- List<Integer> doney = new ArrayList<>();
- for (final Cell cell : cellIter(pageCoords, cellSet)) {
- final List<Integer> coordList = cell.getCoordinateList();
- int y = newyOffset;
- int x = newxOffset;
- if (coordList.size() > 0) {
- if (coordList.get(0) == 0) {
- newxOffset = xOffsset;
- donex = new ArrayList<>();
- }
- x = newxOffset;
- if (coordList.size() > 0)
- x += coordList.get(0);
- y = newyOffset;
- if (coordList.size() > 1)
- y += coordList.get(1);
-
- boolean stop = false;
- if (coordList.size() > 0 && ignorex.contains(coordList.get(0))) {
- if (!donex.contains(coordList.get(0))) {
- newxOffset--;
- donex.add(coordList.get(0));
- }
- stop = true;
- }
- if (coordList.size() > 1 && ignorey.contains(coordList.get(1))) {
- if (!doney.contains(coordList.get(1))) {
- newyOffset--;
- doney.add(coordList.get(1));
- }
- stop = true;
- }
- if (stop) {
- continue;
- }
- }
-
- final DataCell cellInfo = new DataCell(true, false, coordList);
- cellInfo.setCoordinates(cell.getCoordinateList());
-
- if (cell.getValue() != null) {
- try {
- cellInfo.setRawNumber(cell.getDoubleValue());
- } catch (Exception e1) {
- }
- }
- String cellValue = cell.getFormattedValue(); // First try to get a
- // formatted value
-
- if (cellValue == null || cellValue.equals("null")) { //$NON-NLS-1$
- cellValue =""; //$NON-NLS-1$
- }
-
- //0值处理 为"null"的时候 ,cellValue.startsWith("| |style=")表示原值为0,直接让其为0
-
- if (cellValue.startsWith("| |style=")) { //$NON-NLS-1$
- cellValue ="0";
- cellInfo.setRawValue(cellValue);
- }
-
- if ( cellValue.length() < 1) {
- final Object value = cell.getValue();
- if (value == null || value.equals("null")) //$NON-NLS-1$
- cellValue = ""; //$NON-NLS-1$
- else {
- try {
- // TODO this needs to become query / execution specific
- DecimalFormat myFormatter = new DecimalFormat(SaikuProperties.formatDefautNumberFormat); //$NON-NLS-1$
- DecimalFormatSymbols dfs = new DecimalFormatSymbols(SaikuProperties.locale);
- myFormatter.setDecimalFormatSymbols(dfs);
- cellValue = myFormatter.format(cell.getValue());
- }
- catch (Exception e) {
- // TODO: handle exception
- }
- }
- // the raw value
- }
-
- // Format string is relevant for Excel export
- // xmla cells can throw an error on this
- try {
- String formatString = (String) cell.getPropertyValue(Property.StandardCellProperty.FORMAT_STRING);
- if (formatString != null && !formatString.startsWith("|")) {
- cellInfo.setFormatString(formatString);
- } else {
- formatString = formatString.substring(1, formatString.length());
- cellInfo.setFormatString(formatString.substring(0, formatString.indexOf("|")));
- }
- } catch (Exception e) {
- // we tried
- }
-
- Map<String, String> cellProperties = new HashMap<>();
- String val = Olap4jUtil.parseFormattedCellValue(cellValue, cellProperties);
-
- //注釋掉properties style=white設置,防止導出的excel 指標信息無線條
- /*if (!cellProperties.isEmpty()) {
- cellInfo.setProperties(cellProperties);
- }*/
- cellInfo.setFormattedValue(val);
- matrix.set(x, y, cellInfo);
- }
- return matrix;
-
- }
-
- /**
- * Populates cells in the matrix corresponding to a particular axis.
- *
- * @param matrix
- * Matrix to populate
- * @param axis
- * Axis
- * @param axisInfo
- * Description of axis
- * @param isColumns
- * True if columns, false if rows
- * @param oldoffset
- * Ordinal of first cell to populate in matrix
- */
- private void populateAxis(final Matrix matrix, final CellSetAxis axis, final AxisInfo axisInfo,
- final boolean isColumns, final int oldoffset) {
- if (axis == null) {
- return;
- }
-
- int offset = oldoffset;
-
- final Member[] prevMembers = new Member[axisInfo.getWidth()];
- final MemberCell[] prevMemberInfo = new MemberCell[axisInfo.getWidth()];
- final Member[] members = new Member[axisInfo.getWidth()];
-
- // For each axis' position
- for (int i = 0; i < axis.getPositions().size(); i++) {
- final int x = offset + i;
- final Position position = axis.getPositions().get(i);
- int yOffset = 0;
- final List<Member> memberList = position.getMembers();
- boolean stop = false;
-
- // For each position's member
- for (int j = 0; j < memberList.size(); j++) {
- Member member = memberList.get(j);
- final AxisOrdinalInfo ordinalInfo = axisInfo.ordinalInfos.get(j);
- List<Integer> depths = ordinalInfo.depths;
- Collections.sort(depths);
-
- // If it is not the last member (the one with the highest depth)
- if (member.getDepth() < Collections.max(depths)) {
- stop = true;
-
- if (isColumns) {
- ignorex.add(i);
- } else {
- ignorey.add(i);
- }
-
- break;
- }
-
- if (ordinalInfo.getDepths().size() > 0 && member.getDepth() < ordinalInfo.getDepths().get(0)) {
- break;
- }
-
- // It stores each position's member in members array sorted by its depth
- final int y = yOffset + ordinalInfo.depths.indexOf(member.getDepth());
- members[y] = member;
- yOffset += ordinalInfo.getWidth();
- }
-
- if (stop) {
- offset--;
- continue;
- }
-
- boolean expanded = false;
- boolean same = true;
-
- for (int y = 0; y < members.length; y++) {
- final MemberCell memberInfo = new MemberCell();
- final Member member = members[y];
-
- // The index of the member on its position
- int index = memberList.indexOf(member);
-
- if (index >= 0) {
- final AxisOrdinalInfo ordinalInfo = axisInfo.ordinalInfos.get(index);
- int depth_i = ordinalInfo.getDepths().indexOf(member.getDepth());
- if (depth_i > 0) {
- expanded = true;
- }
- }
-
- memberInfo.setExpanded(expanded);
- same = same && i > 0 && Olap4jUtil.equal(prevMembers[y], member);
-
- if (member != null) {
- if (x - 1 == offset)
- memberInfo.setLastRow(true);
-
- matrix.setOffset(oldoffset);
- memberInfo.setRawValue(member.getUniqueName());
- memberInfo.setFormattedValue(member.getCaption()); // First try to get a formatted value
- memberInfo.setParentDimension(member.getDimension().getName());
- memberInfo.setUniquename(member.getUniqueName());
- memberInfo.setHierarchy(member.getHierarchy().getUniqueName());
- memberInfo.setLevel(member.getLevel().getUniqueName());
- // try {
- // memberInfo.setChildMemberCount(member.getChildMemberCount());
- // } catch (OlapException e) {
- // e.printStackTrace();
- // throw new RuntimeException(e);
- // }
- // NamedList<Property> values = member.getLevel().getProperties();
- // for(int j=0; j<values.size();j++){
- // String val;
- // try {
- // val = member.getPropertyFormattedValue(values.get(j));
- // } catch (OlapException e) {
- // e.printStackTrace();
- // throw new RuntimeException(e);
- // }
- // memberInfo.setProperty(values.get(j).getCaption(), val);
- // }
-
- // if (y > 0) {
- // for (int previ = y-1; previ >= 0;previ--) {
- // if(prevMembers[previ] != null) {
- // memberInfo.setRightOf(prevMemberInfo[previ]);
- // memberInfo.setRightOfDimension(prevMembers[previ].getDimension().getName());
- // previ = -1;
- // }
- // }
- // }
-
-
- // if (member.getParentMember() != null)
- // memberInfo.setParentMember(member.getParentMember().getUniqueName());
-
- } else {
- memberInfo.setRawValue(null);
- memberInfo.setFormattedValue(null);
- memberInfo.setParentDimension(null);
- }
-
- if (isColumns) {
- memberInfo.setRight(false);
- memberInfo.setSameAsPrev(same);
- if (member != null)
- memberInfo.setParentDimension(member.getDimension().getName());
- matrix.set(x, y, memberInfo);
- } else {
- memberInfo.setRight(false);
- memberInfo.setSameAsPrev(false);
- matrix.set(y, x, memberInfo);
- }
-
- int x_parent = isColumns ? x : y-1;
- int y_parent = isColumns ? y-1 : x;
-
- if (index >= 0) {
- final AxisOrdinalInfo ordinalInfo = axisInfo.ordinalInfos.get(index);
- int depth_i = ordinalInfo.getDepths().indexOf(member.getDepth());
- while (depth_i > 0) {
- depth_i--;
- int parentDepth = (ordinalInfo.getDepths().get(depth_i));
- Member parent = member.getParentMember();
- while (parent != null && parent.getDepth() > parentDepth) {
- parent = parent.getParentMember();
- }
- final MemberCell pInfo = new MemberCell();
- if (parent != null) {
- pInfo.setRawValue(parent.getUniqueName());
- pInfo.setFormattedValue(parent.getCaption()); // First try to get a formatted value
- pInfo.setParentDimension(parent.getDimension().getName());
- pInfo.setHierarchy(parent.getHierarchy().getUniqueName());
- pInfo.setUniquename(parent.getUniqueName());
- pInfo.setLevel(parent.getLevel().getUniqueName());
- } else {
- pInfo.setRawValue("");
- pInfo.setFormattedValue(""); // First try to get a formatted value
- pInfo.setParentDimension(member.getDimension().getName());
- pInfo.setHierarchy(member.getHierarchy().getUniqueName());
- pInfo.setLevel(member.getLevel().getUniqueName());
- pInfo.setUniquename("");
- }
- matrix.set(x_parent, y_parent, pInfo);
- if (isColumns) {
- y_parent--;
- } else {
- x_parent--;
- }
- }
- }
-
- prevMembers[y] = member;
- prevMemberInfo[y] = memberInfo;
- members[y] = null;
- }
- }
- }
- }
saiku.table.css
- .workspace_results {
- overflow: auto;
- margin-right: 10px;
- margin-left: 10px;
- padding-top: 10px;
- line-height: 1.3;
- min-height: 708px;
- }
-
- .workspace_results table {
- width: auto;
- margin-bottom: 0;
- table-layout: fixed;
- border-spacing: 0;
- border-collapse: separate;
- border-right: 1px solid #d5d5d5;
- border-bottom: 1px solid #d5d5d5;
- }
-
- .workspace_results th,
- .workspace_results td {
- padding: 3px 9px 3px 4px;
- }
-
- .workspace_results td {
- font-size: 10px;
- }
-
- .workspace_results table th {
- margin-bottom: 0;
- font-weight: 500;
- border-left: 1px solid #d5d5d5;
- white-space: normal;
- }
-
- .workspace_results table th.all_null {
- border: none;
- text-align: center;
- }
-
- .workspace_results table th.col {
- border-top: 1px solid #d5d5d5;
- background: #f9f9f9;
- text-align: left;
- }
-
- .workspace_results table th.col_total_corner {
- border-top: 1px solid #d5d5d5;
- border-bottom: 1px none #d5d5d5;
- border-left: 1px none #d5d5d5;
- background: #f9f9f9;
- text-align: left;
- }
-
- .workspace_results table th.col_total_first {
- border-top: 1px solid #d5d5d5;
- border-left: 1px solid #d5d5d5;
- background: #f9f9f9;
- text-align: left;
- }
-
- .workspace_results table th.col_null {
- background: #f9f9f9;
- }
-
- .workspace_results table th.row {
- border-top: 1px solid #d5d5d5;
- background: #f9f9f9;
- text-align: left;
- white-space: nowrap;
- }
-
- .workspace_results table th.row_total {
- border-top: 1px solid #d5d5d5;
- border-left: 1px none #d5d5d5;
- background: #f9f9f9;
- text-align: left;
- }
-
- .workspace_results table th.row_total_first {
- border-top: 1px solid #d5d5d5;
- border-left: 1px solid #d5d5d5;
- background: #f9f9f9;
- text-align: left;
- }
-
- .workspace_results table th.row_total_corner {
- border-top: 1px none #d5d5d5;
- border-right: 1px none #d5d5d5;
- border-bottom: 1px none #d5d5d5;
- border-left: 1px solid #d5d5d5;
- background: #f9f9f9;
- text-align: left;
- }
-
- .workspace_results table th.row_header {
- border-top: 1px solid #d5d5d5;
- /*border-bottom: 1px solid #d5d5d5;*/
- background: #ececec;
- }
-
- .workspace_results table th.row_null {
- background: #f9f9f9;
- }
-
- .workspace_results table td.data {
- border-top: 1px solid #d5d5d5;
- border-left: 1px solid #d5d5d5;
- text-align: right;
- white-space: nowrap;
- }
-
- .workspace_results table td.total {
- font-weight: bold;
- background: #fbfbfb;
- }
-
- .workspace_results table tbody tr:hover {
- background: #edf4fa !important;
- }
-
- .workspace_results table td.cellhighlight:hover {
- background: #c2d5e2 !important;
- }
-
- .workspace_results table td.cellhighlight {
- cursor: pointer;
- }
-
- .workspace_results .headerhighlight th.row:hover,
- .workspace_results .headerhighlight th.col:hover {
- background: #c2d5e2 !important;
- }
-
- .workspace_results .headerhighlight th.row,
- .workspace_results .headerhighlight th.col {
- cursor: pointer;
- }
-
- .workspace_results table td.selected,
- .workspace_results table td.ui-selected,
- .workspace_results table td.ui-selecting {
- background: #c2d5e2 !important;
- }
-
- /* For some weird reason you can not have all css queries
- separated by commas, and have just one of these: */
-
- .workspace_results:fullscreen {
- width: 100%;
- height: 100%;
- background-color: #fff;
- font-weight: 500;
- }
-
- .workspace_results:-moz-full-screen {
- width: 100%;
- height: 100%;
- background-color: #fff;
- }
-
- .workspace_results:-webkit-full-screen {
- width: 100%;
- height: 100%;
- background-color: #fff;
- }
-
- .workspace_results:fullscreen table {
- margin: 0 auto;
- font-size: 200%;
- font-weight: 500;
- }
-
- .workspace_results: table td {
- font-size: 100%;
- }
-
- .workspace_results:-webkit-full-screen table {
- margin: 0 auto;
- font-size: 200%;
- font-weight: 500;
- }
-
- .workspace_results:-webkit-full-screen table td {
- font-size: 100%;
- }
-
- .workspace_results:-moz-full-screen table {
- margin: 0 auto;
- font-size: 200%;
- }
-
- .workspace_results:-moz-full-screen table td {
- font-size: 100%;
- }