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 { 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 { 100%; height: 100%; background-color: #fff; font-weight: 500; } .workspace_results:-moz-full-screen { 100%; height: 100%; background-color: #fff; } .workspace_results:-webkit-full-screen { 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%; }