zoukankan      html  css  js  c++  java
  • Saiku导出excel指标列无线条以及0与空值显示问题(三十二)

    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%;
    }
    

      

  • 相关阅读:
    [Luogu5042/UOJ #100][国家集训队互测2015]丢失的题面/ydc的题面
    [51nod1773]A国的贸易
    [GZOI2019&GXOI2019]省选GG记
    [51nod1659]数方块
    [51nod1052]最大M子段和
    [51nod1201]整数划分
    [51nod1084]矩阵取数问题 V2
    [51nod1020]逆序排列
    [BZOJ3000]Big Number
    [BZOJ1684][Usaco2005 Oct]Close Encounter
  • 原文地址:https://www.cnblogs.com/DFX339/p/10966994.html
Copyright © 2011-2022 走看看