当前位置:   article > 正文

Saiku导出excel指标列无线条以及0与空值显示问题(三十二)

saiku导出excel文件为空

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

  1. if (val == " " || val == '') {
  2.   val=0;
  3. }

  

添加位置可参考下图: (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

  1. function SaikuTableRenderer(data, options) {
  2. this._data = data;
  3. this._options = _.extend({}, SaikuRendererOptions, options);
  4. }
  5. function getAxisLevelsName(data, axisName) {
  6. var queryData = data.query.queryModel.axes[axisName].hierarchies;
  7. var len = queryData.length;
  8. var arrLevels = [];
  9. for (var i = 0; i < len; i++) {
  10. for (var level in queryData[i].levels) {
  11. if (queryData[i].levels.hasOwnProperty(level)) {
  12. if (Settings.COLUMN_TITLE_TABLE_USE_LEVEL_CAPTION_NAME) {
  13. arrLevels.push(queryData[i].levels[level].caption);
  14. }
  15. else {
  16. arrLevels.push(level);
  17. }
  18. }
  19. }
  20. }
  21. return arrLevels;
  22. }
  23. function setStyleNegativeNumber(value) {
  24. var className = '';
  25. if (Settings.STYLE_NEGATIVE_NUMBER && parseFloat(value) < 0) {
  26. className = ' style_negative_number ';
  27. }
  28. return className;
  29. }
  30. function getAxisSize(data, axisName) {
  31. var queryData = data.query.queryModel.axes[axisName].hierarchies;
  32. var len = queryData.length;
  33. var axisSize = 0;
  34. for (var i = 0; i < len; i++) {
  35. axisSize += _.size(queryData[i].levels);
  36. }
  37. return axisSize;
  38. }
  39. function getDomColumnsLevelsName(htmlObject) {
  40. var $htmlObject = $(htmlObject.closest('.workspace')
  41. .find('.workspace_fields')
  42. .find('.columns.axis_fields')
  43. .find('.hierarchy')
  44. .find('.d_level'));
  45. var arrLevels = [];
  46. $.each($htmlObject, function(key, level) {
  47. if ($(level).attr('style') === 'display: list-item;') {
  48. if (Settings.COLUMN_TITLE_TABLE_USE_LEVEL_CAPTION_NAME) {
  49. arrLevels.push($(level).find('.level').attr('title'));
  50. }
  51. else {
  52. arrLevels.push($(level).find('.level').attr('level'));
  53. }
  54. }
  55. });
  56. return arrLevels;
  57. }
  58. /*table render method*/
  59. SaikuTableRenderer.prototype.render = function(data, options) {
  60. var self = this;
  61. if (data) {
  62. this._data = data;
  63. }
  64. if (options) {
  65. this._options = _.extend({}, SaikuRendererOptions, options);
  66. }
  67. if (typeof this._data == "undefined") {
  68. return;
  69. }
  70. if (this._data != null && this._data.error != null) {
  71. return;
  72. }
  73. if (this._data == null || (this._data.cellset && this._data.cellset.length === 0)) {
  74. return;
  75. }
  76. this.hideEmpty = this._options.hideEmpty;
  77. if (this._options.htmlObject) {
  78. // $(this._options.htmlObject).stickyTableHeaders("destroy");
  79. // in case we have some left over scrollers
  80. if (self._options.hasOwnProperty('batch')) {
  81. $(self._options.htmlObject).parent().parent().unbind('scroll');
  82. }
  83. _.defer(function(that) {
  84. if (self._options.hasOwnProperty('batch') && !self._options.hasOwnProperty('batchSize')) {
  85. self._options['batchSize'] = 1000;
  86. }
  87. // the key method to render data by table form. 20190423
  88. var html = self.internalRender(self._data, self._options);
  89. $(self._options.htmlObject).html(html);
  90. // Render the totals summary
  91. $('#totals_summary').remove(); // Remove one previous totals div, if present
  92. $(self._options.htmlObject).after(self.renderSummary(data)); // Render the new summary
  93. // $(self._options.htmlObject).stickyTableHeaders( { container: self._options.htmlObject.parent().parent(), fixedOffset: self._options.htmlObject.parent().parent().offset().top });
  94. _.defer(function(that) {
  95. if (self._options.hasOwnProperty('batch') && self._options.hasBatchResult) {
  96. var batchRow = 0;
  97. var batchIsRunning = false;
  98. var batchIntervalSize = self._options.hasOwnProperty('batchIntervalSize') ? self._options.batchIntervalSize : 20;
  99. var batchIntervalTime = self._options.hasOwnProperty('batchIntervalTime') ? self._options.batchIntervalTime : 20;
  100. var len = self._options.batchResult.length;
  101. var batchInsert = function() {
  102. // maybe add check for reach table bottom - ($('.workspace_results').scrollTop() , $('.workspace_results table').height()
  103. if (!batchIsRunning && len > 0 && batchRow < len) {
  104. batchIsRunning = true;
  105. var batchContent = "";
  106. var startb = batchRow;
  107. for (var i = 0; batchRow < len && i < batchIntervalSize ; i++, batchRow++) {
  108. batchContent += self._options.batchResult[batchRow];
  109. }
  110. if (batchRow > startb) {
  111. $(self._options.htmlObject).append( $(batchContent));
  112. }
  113. batchIsRunning = false;
  114. }
  115. if (batchRow >= len) {
  116. $(self._options.htmlObject).parent().parent().unbind('scroll');
  117. }
  118. };
  119. var lazyBatchInsert = _.debounce(batchInsert, batchIntervalTime);
  120. $(self._options.htmlObject).parent().parent().scroll(function () {
  121. lazyBatchInsert();
  122. });
  123. }
  124. });
  125. return html;
  126. });
  127. } else {
  128. var html = this.internalRender(this._data, self._options);
  129. return html;
  130. }
  131. };
  132. SaikuTableRenderer.prototype.clear = function(data, options) {
  133. var self = this;
  134. if (this._options && this._options.htmlObject && this._options.hasOwnProperty('batch')) {
  135. $(self._options.htmlObject).parent().parent().unbind('scroll');
  136. }
  137. };
  138. SaikuTableRenderer.prototype.processData = function(data, options) {
  139. this._hasProcessed = true;
  140. };
  141. function genTotalDataCells(currentIndex, cellIndex, scanSums, scanIndexes, lists) {
  142. var contents = '';
  143. var lists = lists[ROWS];
  144. for (var i = scanSums.length - 1; i >= 0; i--) {
  145. if (currentIndex == scanSums[i]) {
  146. var currentListNode = lists[i][scanIndexes[i]];
  147. for (var m = 0; m < currentListNode.cells.length; m++) {
  148. contents += '<td class="data total">' + currentListNode.cells[m][cellIndex].value + '</td>';
  149. }
  150. scanIndexes[i]++;
  151. if (scanIndexes[i] < lists[i].length)
  152. scanSums[i] += lists[i][scanIndexes[i]].width;
  153. }
  154. }
  155. return contents;
  156. }
  157. function genTotalHeaderCells(currentIndex, bottom, scanSums, scanIndexes, lists, wrapContent) {
  158. var contents = '';
  159. for (var i = bottom; i >= 0; i--) {
  160. if (currentIndex == scanSums[i]) {
  161. var currentListNode = lists[i][scanIndexes[i]];
  162. var cssClass;
  163. if (i == 0 && bottom == 1)
  164. cssClass = "col";
  165. else if (i == bottom)
  166. cssClass = "col_total_corner";
  167. else if (i == bottom - 1 && currentListNode.captions)
  168. cssClass = "col_total_first";
  169. else cssClass = "col_null";
  170. for (var m = 0; m < currentListNode.cells.length; m++) {
  171. var text = ' ';
  172. if (bottom == lists.length - 1) {
  173. if (currentListNode.captions) {
  174. text = lists[i][scanIndexes[i]].captions[m];
  175. }
  176. if (i == 0 && scanIndexes[i] == 0) {
  177. if (currentListNode.captions)
  178. text += " ";
  179. else text = "";
  180. text += (wrapContent ? "<span class='i18n'>Grand Total</span>" : "Grand Total");
  181. }
  182. }
  183. contents += '<th class="' + cssClass + '">'
  184. + (wrapContent ? '<div>' + text + '</div>' : text ) + '</th>';
  185. }
  186. scanIndexes[i]++;
  187. if (scanIndexes[i] < lists[i].length)
  188. scanSums[i] += lists[i][scanIndexes[i]].width;
  189. }
  190. }
  191. return contents;
  192. }
  193. function totalIntersectionCells(currentIndex, bottom, scanSums, scanIndexes, lists) {
  194. var contents = '';
  195. for (var i = bottom; i >= 0; i--) {
  196. if (currentIndex == scanSums[i]) {
  197. var currentListNode = lists[i][scanIndexes[i]];
  198. var cssClass = "data total";
  199. for (var m = 0; m < currentListNode.cells.length; m++) {
  200. var text = ' ';
  201. contents += '<td class="' + cssClass + '">' + text + '</td>';
  202. }
  203. scanIndexes[i]++;
  204. if (scanIndexes[i] < lists[i].length)
  205. scanSums[i] += lists[i][scanIndexes[i]].width;
  206. }
  207. }
  208. return contents;
  209. }
  210. function isNextTotalsRow(currentIndex, scanSums, scanIndexes, totalsLists, wrapContent) {
  211. var colLists = totalsLists[COLUMNS];
  212. var colScanSums = scanSums[COLUMNS];
  213. var colScanIndexes = scanIndexes[COLUMNS];
  214. var bottom = colLists.length - 2;
  215. var contents = -1;
  216. for (var i = bottom; i >= 0; i--) {
  217. if (currentIndex == colScanSums[i]) {
  218. for (var m = 0; m < colLists[i][colScanIndexes[i]].cells.length; m++) {
  219. contents += '<tr>';
  220. for (var j = 0; j <= bottom; j++) {
  221. var cssClass;
  222. var text = ' ';
  223. if (i == 0 && j == 0)
  224. cssClass = 'row';
  225. else if (i == j + 1){
  226. cssClass = 'row_total_corner';
  227. return j;
  228. }
  229. else if (i == j && colLists[i][colScanIndexes[i]].captions) {
  230. cssClass = 'row_total_first';
  231. } else if (i < j + 1)
  232. cssClass = 'row_total';
  233. else
  234. cssClass = 'row_null';
  235. if (j == bottom ) {
  236. if (colLists[i][colScanIndexes[i]].captions) {
  237. text = colLists[i][colScanIndexes[i]].captions[m];
  238. }
  239. if (i == 0 && colScanIndexes[i] == 0) {
  240. if (colLists[i][colScanIndexes[i]].captions)
  241. text += " ";
  242. else text = "";
  243. text += (wrapContent ? "<span class='i18n'>Grand Total</span>" : "Grand Total");
  244. }
  245. }
  246. }
  247. }
  248. }
  249. }
  250. return -1;
  251. }
  252. function genTotalHeaderRowCells(currentIndex, scanSums, scanIndexes, totalsLists, wrapContent) {
  253. var colLists = totalsLists[COLUMNS];
  254. var colScanSums = scanSums[COLUMNS];
  255. var colScanIndexes = scanIndexes[COLUMNS];
  256. var bottom = colLists.length - 2;
  257. var contents = '';
  258. for (var i = bottom; i >= 0; i--) {
  259. if (currentIndex == colScanSums[i]) {
  260. for (var m = 0; m < colLists[i][colScanIndexes[i]].cells.length; m++) {
  261. contents += '<tr>';
  262. for (var j = 0; j <= bottom; j++) {
  263. var cssClass;
  264. var text = ' ';
  265. if (i == 0 && j == 0)
  266. cssClass = 'row';
  267. else if (i == j + 1)
  268. cssClass = 'row_total_corner';
  269. else if (i == j && colLists[i][colScanIndexes[i]].captions) {
  270. cssClass = 'row_total_first';
  271. } else if (i < j + 1)
  272. cssClass = 'row_total';
  273. else
  274. cssClass = 'row_null';
  275. if (j == bottom ) {
  276. if (colLists[i][colScanIndexes[i]].captions) {
  277. text = colLists[i][colScanIndexes[i]].captions[m];
  278. }
  279. if (i == 0 && colScanIndexes[i] == 0) {
  280. if (colLists[i][colScanIndexes[i]].captions)
  281. text += " ";
  282. else text = "";
  283. text += (wrapContent ? "<span class='i18n'>Grand Total</span>" : "Grand Total");
  284. }
  285. }
  286. contents += '<th class="' + cssClass + '">'
  287. + (wrapContent ? '<div>' + text + '</div>' : text ) + '</th>';
  288. }
  289. var scanIndexes = {};
  290. var scanSums = {};
  291. if (totalsLists[ROWS]) {
  292. for (var z = 0; z < totalsLists[ROWS].length; z++) {
  293. scanIndexes[z] = 0;
  294. scanSums[z] = totalsLists[ROWS][z][scanIndexes[z]].width;
  295. }
  296. }
  297. for (var k = 0; k < colLists[i][colScanIndexes[i]].cells[m].length; k++) {
  298. contents += '<td class="data total">' + colLists[i][colScanIndexes[i]].cells[m][k].value + '</td>';
  299. if (totalsLists[ROWS]) {
  300. contents += totalIntersectionCells(k + 1, totalsLists[ROWS].length - 1, scanSums, scanIndexes, totalsLists[ROWS]);
  301. }
  302. }
  303. contents += '</tr>';
  304. }
  305. colScanIndexes[i]++;
  306. if (colScanIndexes[i] < colLists[i].length) {
  307. colScanSums[i] += colLists[i][colScanIndexes[i]].width;
  308. }
  309. }
  310. }
  311. return contents;
  312. }
  313. var ROWS = "ROWS";
  314. var COLUMNS = "COLUMNS";
  315. function nextParentsDiffer(data, row, col) {
  316. while (row-- > 0) {
  317. if (data[row][col].properties.uniquename != data[row][col + 1].properties.uniquename)
  318. return true;
  319. }
  320. return false;
  321. }
  322. function topParentsDiffer(data, row, col) {
  323. while (col-- > 0)
  324. if (data[row][col].properties.uniquename != data[row - 1][col].properties.uniquename)
  325. return true;
  326. return false;
  327. }
  328. /**
  329. * This function is intended to traverse the totals arrays and cleanup empty
  330. * totals. This will optimize the query result on screen, displaying just the
  331. * needed cells.
  332. * @param dirs The direction array ['ROWS', 'COLUMNS']
  333. * @param totalsLists The totals from allData.rowTotalsLists and allData.colTotalsLists.
  334. */
  335. function cleanupTotals(dirs, totalsLists) {
  336. // For each direction (ROWS/COLUMNS)
  337. for (var dirIndex = 0; dirIndex < dirs.length; dirIndex++) {
  338. var dir = dirs[dirIndex];
  339. // If there are defined totals
  340. if (totalsLists[dir]) {
  341. var isEmpty = true; // A flag to indicate if this total is empty
  342. for (var row = 0; row < totalsLists[dir].length; row++) {
  343. var totalsInfoArray = totalsLists[dir][row];
  344. for (var totalIndex = 0; totalIndex < totalsInfoArray.length; totalIndex++) {
  345. var cells = totalsLists[dir][row][totalIndex].cells;
  346. for (var cellIndex = 0; cellIndex < cells.length; cellIndex++) {
  347. var cellArray = cells[cellIndex];
  348. // For each total cell
  349. for (var i = 0; i < cellArray.length; i++) {
  350. var cell = cellArray[i];
  351. // If it contains a value different from empty
  352. if (cell.value !== '-') {
  353. isEmpty = false; // So, this total is not empty
  354. }
  355. }
  356. }
  357. }
  358. }
  359. if (isEmpty) { // If this total is empty
  360. totalsLists[dir] = null; // Remove it
  361. }
  362. }
  363. }
  364. }
  365. /*the main method to render data by table form. 20190423*/
  366. SaikuTableRenderer.prototype.internalRender = function(allData, options) {
  367. var tableContent = "";
  368. var rowContent = "";
  369. var data = allData.cellset;
  370. var newRowContent = '';
  371. var arrRowData = [];
  372. var objRowData = [];
  373. var table = data ? data : [];
  374. var colSpan;
  375. var colValue;
  376. var isHeaderLowestLvl;
  377. var isBody = false;
  378. var firstColumn;
  379. var isLastColumn, isLastRow;
  380. var nextHeader;
  381. var processedRowHeader = false;
  382. var lowestRowLvl = 0;
  383. var rowGroups = [];
  384. var batchSize = null;
  385. var batchStarted = false;
  386. var isColHeader = false, isColHeaderDone = false;
  387. var resultRows = [];
  388. var wrapContent = true;
  389. if (options) {
  390. batchSize = options.hasOwnProperty('batchSize') ? options.batchSize : null;
  391. wrapContent = options.hasOwnProperty('wrapContent') ? options.wrapContent : true;
  392. }
  393. var totalsLists = {};
  394. totalsLists[COLUMNS] = allData.rowTotalsLists;
  395. totalsLists[ROWS] = allData.colTotalsLists;
  396. var scanSums = {};
  397. var scanIndexes = {};
  398. var dirs = [ROWS, COLUMNS];
  399. var hasMeasures = allData.query && allData.query.queryModel && allData.query.queryModel.details
  400. ? allData.query.queryModel.details.measures.length
  401. : 0;
  402. if (typeof this._options.htmlObject === 'object' &&
  403. Settings.ALLOW_AXIS_COLUMN_TITLE_TABLE &&
  404. hasMeasures > 0 &&
  405. allData.query.type === 'QUERYMODEL' &&
  406. allData.query.queryModel.details.axis === 'COLUMNS' &&
  407. allData.query.queryModel.details.location === 'BOTTOM') {
  408. var arrColumnTitleTable = getAxisLevelsName(allData, COLUMNS);
  409. var arrDomColumnTitleTable = getDomColumnsLevelsName(this._options.htmlObject);
  410. var colspanColumnTitleTable = getAxisSize(allData, ROWS);
  411. var auxColumnTitleTable = 0;
  412. if (arrColumnTitleTable.length === arrDomColumnTitleTable.length) {
  413. arrColumnTitleTable = arrDomColumnTitleTable;
  414. }
  415. else {
  416. arrColumnTitleTable = _.intersection(arrDomColumnTitleTable, arrColumnTitleTable);
  417. }
  418. }
  419. for (var i = 0; i < dirs.length; i++) {
  420. scanSums[dirs[i]] = new Array();
  421. scanIndexes[dirs[i]] = new Array();
  422. }
  423. // Here we cleaup the empty totals
  424. cleanupTotals(dirs, totalsLists);
  425. if (totalsLists[COLUMNS]) {
  426. for (var i = 0; i < totalsLists[COLUMNS].length; i++) {
  427. scanIndexes[COLUMNS][i] = 0;
  428. scanSums[COLUMNS][i] = totalsLists[COLUMNS][i][scanIndexes[COLUMNS][i]].width;
  429. }
  430. }
  431. var headerFlag=true;// add this flag to solve the bug when same data to merge。 20190423
  432. var cubename = allData.query.cube.name;
  433. for (var row = 0, rowLen = table.length; row < rowLen; row++) {
  434. var rowShifted = row - allData.topOffset;
  435. colSpan = 1;
  436. colValue = "";
  437. isHeaderLowestLvl = false;
  438. isLastColumn = false;
  439. isLastRow = false;
  440. isColHeader = false;
  441. var headerSame = false;
  442. if (totalsLists[ROWS]) {
  443. for (var i = 0; i < totalsLists[ROWS].length; i++) {
  444. scanIndexes[ROWS][i] = 0;
  445. scanSums[ROWS][i] = totalsLists[ROWS][i][scanIndexes[ROWS][i]].width;
  446. }
  447. }
  448. rowWithOnlyEmptyCells = true; //默认行数据为空时不展展示,如果改回来 将此条值置为true即可
  449. //var paramsURI = Saiku.URLParams.paramsURI(); //get the param from url
  450. //var showEmptyRows = paramsURI.showEmptyRows;
  451. //if(showEmptyRows != null && showEmptyRows != undefined && showEmptyRows != '' && showEmptyRows=='yes') {
  452. if(cubename == "SummaryKPI_2018_ext" ||cubename == "SummaryKPI_2019_ext" ||cubename == "SummaryKPI_2019_Dynamic"){
  453. rowWithOnlyEmptyCells = false; //設置行數據為空的時候也顯示數據! 20190425 for summaryKPI Data
  454. }
  455. rowContent = "<tr>";
  456. var header = null;
  457. if (row === 0) {
  458. rowContent = "<thead>" + rowContent;
  459. }
  460. if (typeof this._options.htmlObject === 'object' &&
  461. Settings.ALLOW_AXIS_COLUMN_TITLE_TABLE &&
  462. hasMeasures > 0 &&
  463. allData.query.type === 'QUERYMODEL' &&
  464. allData.query.queryModel.details.axis === 'COLUMNS' &&
  465. allData.query.queryModel.details.location === 'BOTTOM' &&
  466. auxColumnTitleTable < arrColumnTitleTable.length) {
  467. rowContent += '<th class="row_header" style="text-align: right;" colspan="' + colspanColumnTitleTable + '" title="' + arrColumnTitleTable[auxColumnTitleTable] + '">'
  468. + (wrapContent ? '<div>' + arrColumnTitleTable[auxColumnTitleTable] + '</div>' : arrColumnTitleTable[auxColumnTitleTable])
  469. + '</th>';
  470. auxColumnTitleTable += 1;
  471. }
  472. //循環遍歷data 將帶小數的年、月去掉小數位; 例如 2018.0 轉換為 2018
  473. for (var col = 0, colLen = table[row].length; col < colLen; col++) {
  474. var tmpRow = data[row][col];
  475. if(tmpRow.properties.hierarchy=="[年].[年]" || tmpRow.properties.hierarchy=="[月].[月]"){
  476. var tmpValue = tmpRow.value;
  477. if(tmpValue != null && tmpValue.indexOf('.')>-1){
  478. tmpValue = tmpValue.substr(0,tmpValue.indexOf('.'));//去掉小數位
  479. data[row][col].value = tmpValue;
  480. }
  481. }
  482. }
  483. for (var col = 0, colLen = table[row].length; col < colLen; col++) {
  484. var colShifted = col - allData.leftOffset;
  485. header = data[row][col];
  486. if (header.type === "COLUMN_HEADER") {
  487. isColHeader = true;
  488. }
  489. // If the cell is a column header and is null (top left of table)
  490. if (header.type === "COLUMN_HEADER" && header.value === "null" && (firstColumn == null || col < firstColumn)) {
  491. if (((!Settings.ALLOW_AXIS_COLUMN_TITLE_TABLE || (Settings.ALLOW_AXIS_COLUMN_TITLE_TABLE && allData.query.queryModel.details.location !== 'BOTTOM')) || hasMeasures === 0) ||
  492. allData.query.type === 'MDX') {
  493. rowContent += '<th class="all_null"> </th>';
  494. }
  495. } // If the cell is a column header and isn't null (column header of table)
  496. else if (header.type === "COLUMN_HEADER") {
  497. if (firstColumn == null) {
  498. firstColumn = col;
  499. }
  500. if (table[row].length == col+1)
  501. isLastColumn = true;
  502. else
  503. nextHeader = data[row][col+1];
  504. if (isLastColumn) {
  505. // Last column in a row...
  506. if (header.value == "null") {
  507. rowContent += '<th class="col_null"> </th>';
  508. } else {
  509. if (totalsLists[ROWS])
  510. colSpan = totalsLists[ROWS][row + 1][scanIndexes[ROWS][row + 1]].span;
  511. rowContent += '<th class="col" style="text-align: center;" colspan="' + colSpan + '" title="' + header.value + '">'
  512. + (wrapContent ? '<div rel="' + row + ":" + col +'">' + header.value + '</div>' : header.value)
  513. + '</th>';
  514. }
  515. } else {
  516. // All the rest...
  517. var groupChange = (col > 1 && row > 1 && !isHeaderLowestLvl && col > firstColumn) ?
  518. data[row-1][col+1].value != data[row-1][col].value || data[row-1][col+1].properties.uniquename != data[row-1][col].properties.uniquename
  519. : false;
  520. var maxColspan = colSpan > 999 ? true : false;
  521. if (header.value != nextHeader.value || nextParentsDiffer(data, row, col) || isHeaderLowestLvl || groupChange || maxColspan) {
  522. if (header.value == "null") {
  523. rowContent += '<th class="col_null" colspan="' + colSpan + '"> </th>';
  524. } else {
  525. if (totalsLists[ROWS])
  526. colSpan = totalsLists[ROWS][row + 1][scanIndexes[ROWS][row + 1]].span;
  527. rowContent += '<th class="col" style="text-align: center;" colspan="' + (colSpan == 0 ? 1 : colSpan) + '" title="' + header.value + '">'
  528. + (wrapContent ? '<div rel="' + row + ":" + col +'">' + header.value + '</div>' : header.value)
  529. + '</th>';
  530. }
  531. colSpan = 1;
  532. } else {
  533. colSpan++;
  534. }
  535. }
  536. if (totalsLists[ROWS])
  537. rowContent += genTotalHeaderCells(col - allData.leftOffset + 1, row + 1, scanSums[ROWS], scanIndexes[ROWS], totalsLists[ROWS], wrapContent);
  538. } // If the cell is a row header and is null (grouped row header)
  539. else if (header.type === "ROW_HEADER" && header.value === "null") {
  540. rowContent += '<th class="row_null"> </th>';
  541. } // If the cell is a row header and isn't null (last row header)
  542. else if (header.type === "ROW_HEADER") {
  543. if (lowestRowLvl == col)
  544. isHeaderLowestLvl = true;
  545. else
  546. nextHeader = data[row][col+1];
  547. var previousRow = data[row - 1];
  548. var nextRow = data[row + 1];
  549. // when same set fixed value is false ,It means the same data will not merge。table data will show row by row.20190423
  550. //var same=false;
  551. /*judge the current value and previousRow value,
  552. if equals ,all set comeback,set the headerFlag is true,
  553. we can judge the data as usual. 20190423*/
  554. if(header.value !== previousRow[col].value){
  555. headerFlag =true;
  556. }
  557. /*judge the tableContent include value or not, if include ,
  558. set the headerFlag value is true to avoid repeat datas showed in table.20190423*/
  559. if(tableContent.indexOf(header.value) > -1 ){
  560. headerFlag =true;
  561. }
  562. /*add headerFlag to judge the data is same ,then control the data merge wheather or not.20190423 */
  563. var same = !headerSame && !isHeaderLowestLvl && (col == 0 || !topParentsDiffer(data, row, col)) && header.value === previousRow[col].value && headerFlag;
  564. headerSame = !same;
  565. var sameAsPrevValue = false;
  566. if(Settings.ALLOW_TABLE_DATA_COLLAPSE){
  567. if (row > 0 && row < rowLen - 1) {
  568. if (totalsLists[ROWS] == null || (col <= colLen - totalsLists[ROWS].length - 1)) {
  569. var checkOther = true;
  570. if (totalsLists[COLUMNS] && rowShifted >= 0 && col <= isNextTotalsRow(rowShifted + 1, scanSums, scanIndexes, totalsLists, wrapContent)) {
  571. sameAsPrevValue = true;
  572. checkOther = false;
  573. }
  574. if (checkOther && nextRow[col].value == header.value) {
  575. if (col > 0) {
  576. for (var j = 0; j < col; j++) {
  577. if (nextRow[j].value == data[row][j].value) {
  578. sameAsPrevValue = true;
  579. } else {
  580. sameAsPrevValue = false;
  581. break;
  582. }
  583. }
  584. } else {
  585. sameAsPrevValue = true;
  586. }
  587. }
  588. }
  589. } else if(row > 0 && row == rowLen - 1) {
  590. if (totalsLists[COLUMNS] && rowShifted >= 0 && col <= isNextTotalsRow(rowShifted + 1, scanSums, scanIndexes, totalsLists, wrapContent)) {
  591. sameAsPrevValue = true;
  592. }
  593. }
  594. }
  595. var value = (same ? "<div> </div>" : '<div rel="' + row + ":" + col + '">'
  596. + (sameAsPrevValue && Settings.ALLOW_TABLE_DATA_COLLAPSE ? '<span class="expander expanded" style="cursor: pointer;">▼</span>' : '' ) + header.value + '</div>');
  597. if (!wrapContent) {
  598. value = (same ? " " : header.value );
  599. }
  600. var tipsy = "";
  601. /* var tipsy = ' original-title="';
  602. if (!same && header.metaproperties) {
  603. for (key in header.metaproperties) {
  604. if (key.substring(0,1) != "$" && key.substring(1,2).toUpperCase() != key.substring(1,2)) {
  605. tipsy += "<b>" + safe_tags_replace(key) + "</b> : " + safe_tags_replace(header.metaproperties[key]) + "<br>";
  606. }
  607. }
  608. }
  609. tipsy += '"';
  610. */
  611. var cssclass = (same ? "row_null" : "row");
  612. var colspan = 0;
  613. if (!isHeaderLowestLvl && (typeof nextHeader == "undefined" || nextHeader.value === "null")) {
  614. colspan = 1;
  615. var group = header.properties.dimension;
  616. var level = header.properties.level;
  617. var groupWidth = (group in rowGroups ? rowGroups[group].length - rowGroups[group].indexOf(level) : 1);
  618. for (var k = col + 1; colspan < groupWidth && k <= (lowestRowLvl+1) && data[row][k] !== "null"; k++) {
  619. colspan = k - col;
  620. }
  621. col = col + colspan -1;
  622. }
  623. /*when the content is to long ,we will set new line to show it.*/
  624. // eg value: <div rel="3:0">新業務及保單行政部</div>
  625. if(cssclass == "row" && value.length>0){
  626. var startPos = value.indexOf('>'); //find start position of the tag. eg: <div rel="3:0">
  627. var endPos = value.lastIndexOf('<'); //find end position of the tag. eg: </div>
  628. var tmpValue = value.substr( startPos+1 ,endPos-startPos-1); // get the content value. eg: 新業務及保單行政部
  629. //将value值每隔40个字自动加上换行符
  630. //each 40 character add one <br> tag to get new line.
  631. if(tmpValue.length>120){
  632. tmpValue = tmpValue.substr(0,40)+"<br/>"+tmpValue.substr(40,40)+"<br/>"+tmpValue.substr(80,40)+"<br/>"+tmpValue.substr(120,tmpValue.length-120);
  633. }else if(tmpValue.length>80){
  634. tmpValue = tmpValue.substr(0,40)+"<br/>"+tmpValue.substr(40,40)+"<br/>"+tmpValue.substr(80,tmpValue.length-80);
  635. }else if(tmpValue.length>40){
  636. tmpValue = tmpValue.substr(0,40)+"<br/>"+tmpValue.substr(40,tmpValue.length-40);
  637. }
  638. // compared with old value, this value only add <br> tag for show data in table more beautiful.
  639. value = value.substr(0,startPos+1) + tmpValue + value.substr(endPos,value.length-endPos);
  640. }
  641. rowContent += '<th class="' + cssclass + '" ' + (colspan > 0 ? ' colspan="' + colspan + '"' : "") + tipsy + '>' + value + '</th>';
  642. }
  643. else if (header.type === "ROW_HEADER_HEADER") {
  644. var hierName = function(data) {
  645. var hier = data.properties.hierarchy;
  646. var name = hier.replace(/[\[\]]/gi, '').split('.')[1]
  647. ? hier.replace(/[\[\]]/gi, '').split('.')[1]
  648. : hier.replace(/[\[\]]/gi, '').split('.')[0];
  649. return name;
  650. };
  651. var arrPosRowData = [];
  652. if (_.contains(arrRowData, header.value)) {
  653. for (var i = 0; i < arrRowData.length; i++) {
  654. if (arrRowData[i] === header.value) {
  655. arrPosRowData.push(i);
  656. }
  657. }
  658. arrPosRowData.push(col);
  659. }
  660. rowContent += '<th class="row_header">' + (wrapContent ? '<div>' + header.value + '</div>' : header.value) + '</th>';
  661. arrRowData.push(header.value);
  662. objRowData.push({
  663. name: header.value,
  664. hierName: hierName(header) + '/' + header.value
  665. });
  666. isHeaderLowestLvl = true;
  667. processedRowHeader = true;
  668. lowestRowLvl = col;
  669. if (header.properties.hasOwnProperty("dimension")) {
  670. var group = header.properties.dimension;
  671. if (!(group in rowGroups)) {
  672. rowGroups[group] = [];
  673. }
  674. rowGroups[group].push(header.properties.level);
  675. }
  676. if (arrPosRowData.length > 0) {
  677. var aux = 0;
  678. rowContent = '<tr>';
  679. if (row === 0) {
  680. rowContent = '<thead>' + rowContent;
  681. }
  682. for (var i = 0; i < objRowData.length; i++) {
  683. if (arrPosRowData[aux] === i) {
  684. newRowContent += '<th class="row_header">' + (wrapContent ? '<div>' + objRowData[i].hierName + '</div>' : objRowData[i].hierName) + '</th>';
  685. aux += 1;
  686. }
  687. else {
  688. newRowContent += '<th class="row_header">' + (wrapContent ? '<div>' + objRowData[i].name + '</div>' : objRowData[i].name) + '</th>';
  689. }
  690. }
  691. rowContent += newRowContent;
  692. }
  693. } // If the cell is a normal data cell
  694. else if (header.type === "DATA_CELL") {
  695. batchStarted = true;
  696. var color = "";
  697. var val = _.isEmpty(header.value) ? Settings.EMPTY_VALUE_CHARACTER : header.value;
  698. var arrow = "";
  699. if (header.properties.hasOwnProperty('image')) {
  700. var img_height = header.properties.hasOwnProperty('image_height') ? " height='" + header.properties.image_height + "'" : "";
  701. var img_width = header.properties.hasOwnProperty('image_width') ? " width='" + header.properties.image_width + "'" : "";
  702. val = "<img " + img_height + " " + img_width + " style='padding-left: 5px' src='" + header.properties.image + "' border='0'>";
  703. }
  704. // Just apply formatting to non-empty cells
  705. if (val !== '-' && val !== '' && header.properties.hasOwnProperty('style')) {
  706. color = " style='background-color: " + header.properties.style + "' ";
  707. }
  708. if (header.properties.hasOwnProperty('link')) {
  709. val = "<a target='__blank' href='" + header.properties.link + "'>" + val + "</a>";
  710. }
  711. if (header.properties.hasOwnProperty('arrow')) {
  712. arrow = "<img height='10' width='10' style='padding-left: 5px' src='./images/arrow-" + header.properties.arrow + ".gif' border='0'>";
  713. }
  714. if (val !== '-' && val !== '') {
  715. rowWithOnlyEmptyCells = false;
  716. }
  717. //设置saiku为0的数据正常显示0 ,不显示为空
  718. if (val == " " || val == '') {
  719. val=0;
  720. }
  721. rowContent += '<td class="data" ' + color + '>'
  722. + (wrapContent ? '<div class="datadiv '+ setStyleNegativeNumber(header.properties.raw) + '" alt="' + header.properties.raw + '" rel="' + header.properties.position + '">' : "")
  723. + val + arrow
  724. + (wrapContent ? '</div>' : '') + '</td>';
  725. if (totalsLists[ROWS])
  726. rowContent += genTotalDataCells(colShifted + 1, rowShifted, scanSums[ROWS], scanIndexes[ROWS], totalsLists, wrapContent);
  727. }
  728. }
  729. rowContent += "</tr>";
  730. // Change it to let hideEmpty true by default
  731. if (options.hideEmpty && header.type === "DATA_CELL" && rowWithOnlyEmptyCells) {
  732. /*when data_cell is null,set the headerFlag is false ,
  733. to fix the problem data merge inccrrect.
  734. */
  735. headerFlag=false;
  736. rowContent = '';
  737. }
  738. var totals = "";
  739. if (totalsLists[COLUMNS] && rowShifted >= 0) {
  740. totals += genTotalHeaderRowCells(rowShifted + 1, scanSums, scanIndexes, totalsLists, wrapContent);
  741. }
  742. if (batchStarted && batchSize) {
  743. if (row <= batchSize) {
  744. if (!isColHeader && !isColHeaderDone) {
  745. tableContent += "</thead><tbody>";
  746. isColHeaderDone = true;
  747. }
  748. tableContent += rowContent;
  749. if (totals.length > 0) {
  750. tableContent += totals;
  751. }
  752. } else {
  753. resultRows.push(rowContent);
  754. if (totals.length > 0) {
  755. resultRows.push(totals);
  756. }
  757. }
  758. } else {
  759. if (!isColHeader && !isColHeaderDone) {
  760. tableContent += "</thead><tbody>";
  761. isColHeaderDone = true;
  762. }
  763. tableContent += rowContent;
  764. if (totals.length > 0) {
  765. tableContent += totals;
  766. }
  767. }
  768. }
  769. if (options) {
  770. options['batchResult'] = resultRows;
  771. options['hasBatchResult'] = resultRows.length > 0;
  772. }
  773. return "<table>" + tableContent + "</tbody></table>";
  774. };
  775. SaikuTableRenderer.prototype.renderSummary = function(data) {
  776. if (data && data.query) {
  777. var hasSomethingToRender = false;
  778. var measures = data.query.queryModel.details
  779. ? data.query.queryModel.details.measures
  780. : [];
  781. var summaryData = {};
  782. for (var i = 0; i < measures.length; i++) {
  783. var m = measures[i];
  784. if (m.aggregators) {
  785. for (var j = 0; j < m.aggregators.length; j++) {
  786. var a = m.aggregators[j];
  787. if (a.indexOf('_') > 0) {
  788. var tokens = a.split('_');
  789. var aggregator = tokens[0];
  790. var axis = tokens[1];
  791. if (aggregator !== 'nil' && aggregator !== 'not') {
  792. hasSomethingToRender = true;
  793. aggregator = aggregator.capitalizeFirstLetter();
  794. if (!(axis in summaryData)) summaryData[axis] = [];
  795. summaryData[axis].push(m.name + ": " + aggregator);
  796. }
  797. }
  798. }
  799. }
  800. }
  801. if (hasSomethingToRender) {
  802. var summary = "<div id='totals_summary'><br/>";
  803. $.each(summaryData, function(key, aggregators) {
  804. summary += "<h3>" + key.capitalizeFirstLetter();
  805. for (var i = 0; i < aggregators.length; i++) {
  806. summary += "<br/> " + aggregators[i];
  807. }
  808. summary += "</h3>";
  809. });
  810. return summary + "</div>";
  811. }
  812. }
  813. return "";
  814. };
  815. String.prototype.capitalizeFirstLetter = function() {
  816. return this.charAt(0).toUpperCase() + this.slice(1).toLowerCase();
  817. }

  

ExcelWorksheetBuilder.java

  1. package org.saiku.service.util.export.excel;
  2. import org.apache.commons.lang.StringUtils;
  3. import org.apache.poi.hssf.usermodel.HSSFPalette;
  4. import org.apache.poi.hssf.usermodel.HSSFWorkbook;
  5. import org.apache.poi.ss.SpreadsheetVersion;
  6. import org.apache.poi.ss.usermodel.*;
  7. import org.apache.poi.ss.usermodel.Font;
  8. import org.apache.poi.ss.util.CellRangeAddress;
  9. import org.apache.poi.xssf.usermodel.XSSFCellStyle;
  10. import org.apache.poi.xssf.usermodel.XSSFColor;
  11. import org.apache.poi.xssf.usermodel.XSSFWorkbook;
  12. import org.olap4j.metadata.Measure;
  13. import org.saiku.olap.dto.resultset.AbstractBaseCell;
  14. import org.saiku.olap.dto.resultset.CellDataSet;
  15. import org.saiku.olap.dto.resultset.DataCell;
  16. import org.saiku.olap.dto.resultset.MemberCell;
  17. import org.saiku.olap.query2.ThinHierarchy;
  18. import org.saiku.olap.query2.ThinLevel;
  19. import org.saiku.olap.query2.ThinMember;
  20. import org.saiku.olap.util.SaikuProperties;
  21. import org.saiku.service.olap.totals.TotalNode;
  22. import org.saiku.service.olap.totals.aggregators.TotalAggregator;
  23. import org.saiku.service.util.exception.SaikuServiceException;
  24. import org.slf4j.Logger;
  25. import org.slf4j.LoggerFactory;
  26. import java.io.ByteArrayOutputStream;
  27. import java.io.IOException;
  28. import java.io.InputStream;
  29. import java.text.SimpleDateFormat;
  30. import java.util.*;
  31. import java.util.List;
  32. /**
  33. * Created with IntelliJ IDEA. User: sramazzina Date: 21/06/12 Time: 7.35 To
  34. * change this template use File | Settings | File Templates.
  35. */
  36. public class ExcelWorksheetBuilder {
  37. private static final String BASIC_SHEET_FONT_FAMILY = "Arial";
  38. private static final short BASIC_SHEET_FONT_SIZE = 11;
  39. private static final String EMPTY_STRING = "";
  40. private static final String CSS_COLORS_CODE_PROPERTIES = "css-colors-codes.properties";
  41. private int maxRows = -1;
  42. private int maxColumns = -1;
  43. private AbstractBaseCell[][] rowsetHeader;
  44. private AbstractBaseCell[][] rowsetBody;
  45. private Map<Integer, TotalAggregator[][]> rowScanTotals;
  46. private Map<Integer, TotalAggregator[][]> colScanTotals;
  47. private CellDataSet table;
  48. private Workbook excelWorkbook;
  49. private Sheet workbookSheet;
  50. private String sheetName;
  51. private int topLeftCornerWidth;
  52. private int topLeftCornerHeight;
  53. private CellStyle basicCS;
  54. private CellStyle totalsCS;
  55. private CellStyle numberCS;
  56. private CellStyle number0CS; //use this cellstyle when cell value is 0
  57. private CellStyle lighterHeaderCellCS;
  58. private List<ThinHierarchy> queryFilters;
  59. private Map<String, Integer> colorCodesMap;
  60. private int nextAvailableColorCode = 41;
  61. private Properties cssColorCodesProperties;
  62. private HSSFPalette customColorsPalette;
  63. private ExcelBuilderOptions options;
  64. private static final Logger log = LoggerFactory.getLogger(ExcelWorksheetBuilder.class);
  65. public ExcelWorksheetBuilder(CellDataSet table, List<ThinHierarchy> filters, ExcelBuilderOptions options) {
  66. init(table, filters, options);
  67. }
  68. private void init(CellDataSet table, List<ThinHierarchy> filters, ExcelBuilderOptions options) {
  69. this.table = table;
  70. this.options = options;
  71. queryFilters = filters;
  72. maxRows = SpreadsheetVersion.EXCEL2007.getMaxRows();
  73. maxColumns = SpreadsheetVersion.EXCEL2007.getMaxColumns();
  74. if ("xls".equals(SaikuProperties.webExportExcelFormat)) {
  75. HSSFWorkbook wb = new HSSFWorkbook();
  76. customColorsPalette = wb.getCustomPalette();
  77. excelWorkbook = wb;
  78. maxRows = SpreadsheetVersion.EXCEL97.getMaxRows();
  79. maxColumns = SpreadsheetVersion.EXCEL97.getMaxColumns();
  80. } else if ("xlsx".equals(SaikuProperties.webExportExcelFormat)) {
  81. excelWorkbook = new XSSFWorkbook();
  82. } else {
  83. excelWorkbook = new XSSFWorkbook();
  84. }
  85. colorCodesMap = new HashMap<>();
  86. this.sheetName = options.sheetName;
  87. rowsetHeader = table.getCellSetHeaders();
  88. rowsetBody = table.getCellSetBody();
  89. topLeftCornerWidth = findTopLeftCornerWidth();
  90. topLeftCornerHeight = findTopLeftCornerHeight();
  91. initCellStyles();
  92. // Row totals and subtotals
  93. rowScanTotals = new HashMap<>();
  94. colScanTotals = new HashMap<>();
  95. scanRowAndColumnAggregations(table.getRowTotalsLists(), rowScanTotals, table.getColTotalsLists(), colScanTotals);
  96. }
  97. private void initCellStyles() {
  98. Font font = excelWorkbook.createFont();
  99. font.setFontHeightInPoints((short) BASIC_SHEET_FONT_SIZE);
  100. font.setFontName(BASIC_SHEET_FONT_FAMILY);
  101. basicCS = excelWorkbook.createCellStyle();
  102. basicCS.setFont(font);
  103. basicCS.setAlignment(HorizontalAlignment.LEFT);
  104. basicCS.setVerticalAlignment(VerticalAlignment.TOP);
  105. setCellBordersColor(basicCS);
  106. Font totalsFont = excelWorkbook.createFont();
  107. totalsFont.setFontHeightInPoints((short) BASIC_SHEET_FONT_SIZE);
  108. totalsFont.setBold(true);
  109. totalsFont.setFontName(BASIC_SHEET_FONT_FAMILY);
  110. totalsCS = excelWorkbook.createCellStyle();
  111. totalsCS.setFont(totalsFont);
  112. totalsCS.setAlignment(HorizontalAlignment.RIGHT);
  113. setCellBordersColor(totalsCS);
  114. // Setting the default styling for number cells
  115. numberCS = excelWorkbook.createCellStyle();
  116. numberCS.setFont(font);
  117. numberCS.setAlignment(HorizontalAlignment.RIGHT);
  118. /*
  119. * justasg: Let's set default format, used if measure has no format at
  120. * all. More info:
  121. * http://poi.apache.org/apidocs/org/apache/poi/ss/usermodel/
  122. * BuiltinFormats.html#getBuiltinFormat(int) If we don't have default
  123. * format, it will output values up to maximum detail, i.e.
  124. * 121212.3456789 and we like them as 121,212.346
  125. */
  126. DataFormat fmt = excelWorkbook.createDataFormat();
  127. short dataFormat = fmt.getFormat(SaikuProperties.webExportExcelDefaultNumberFormat);
  128. numberCS.setDataFormat(dataFormat);
  129. setCellBordersColor(numberCS);
  130. // Setting the default format for cell value is 0
  131. number0CS = excelWorkbook.createCellStyle();
  132. number0CS.setFont(font);
  133. number0CS.setAlignment(HorizontalAlignment.RIGHT);
  134. setCellBordersColor(number0CS);
  135. Font headerFont = excelWorkbook.createFont();
  136. headerFont.setFontHeightInPoints((short) BASIC_SHEET_FONT_SIZE);
  137. headerFont.setFontName(BASIC_SHEET_FONT_FAMILY);
  138. headerFont.setBold(true);
  139. lighterHeaderCellCS = excelWorkbook.createCellStyle();
  140. lighterHeaderCellCS.setFont(headerFont);
  141. lighterHeaderCellCS.setAlignment(HorizontalAlignment.CENTER);
  142. lighterHeaderCellCS.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
  143. lighterHeaderCellCS.setFillPattern(FillPatternType.SOLID_FOREGROUND);
  144. setCellBordersColor(lighterHeaderCellCS);
  145. CellStyle darkerHeaderCellCS = excelWorkbook.createCellStyle();
  146. darkerHeaderCellCS.setFont(headerFont);
  147. darkerHeaderCellCS.setAlignment(HorizontalAlignment.CENTER);
  148. darkerHeaderCellCS.setFillForegroundColor(IndexedColors.GREY_40_PERCENT.getIndex());
  149. darkerHeaderCellCS.setFillPattern(FillPatternType.SOLID_FOREGROUND);
  150. setCellBordersColor(darkerHeaderCellCS);
  151. }
  152. //更改指标分割线条颜色为 黑色20190528
  153. private void setCellBordersColor(CellStyle style) {
  154. style.setBorderBottom(BorderStyle.THIN);
  155. style.setBottomBorderColor(IndexedColors.BLACK.getIndex());
  156. style.setBorderTop(BorderStyle.THIN);
  157. style.setTopBorderColor(IndexedColors.BLACK.getIndex());
  158. style.setBorderLeft(BorderStyle.THIN);
  159. style.setLeftBorderColor(IndexedColors.BLACK.getIndex());
  160. style.setBorderRight(BorderStyle.THIN);
  161. style.setRightBorderColor(IndexedColors.BLACK.getIndex());
  162. /*style.setBorderBottom(BorderStyle.THIN);
  163. style.setBottomBorderColor(IndexedColors.GREY_80_PERCENT.getIndex());
  164. style.setBorderTop(BorderStyle.THIN);
  165. style.setTopBorderColor(IndexedColors.GREY_80_PERCENT.getIndex());
  166. style.setBorderLeft(BorderStyle.THIN);
  167. style.setLeftBorderColor(IndexedColors.GREY_80_PERCENT.getIndex());
  168. style.setBorderRight(BorderStyle.THIN);
  169. style.setRightBorderColor(IndexedColors.GREY_80_PERCENT.getIndex());*/
  170. }
  171. public byte[] build() throws SaikuServiceException {
  172. Long start = (new Date()).getTime();
  173. int startRow = initExcelSheet();
  174. Long init = (new Date()).getTime();
  175. int lastHeaderRow = buildExcelTableHeader(startRow);
  176. Long header = (new Date()).getTime();
  177. addExcelTableRows(lastHeaderRow);
  178. addTotalsSummary(lastHeaderRow);
  179. Long content = (new Date()).getTime();
  180. finalizeExcelSheet(startRow);
  181. Long finalizing = (new Date()).getTime();
  182. log.debug("Init: " + (init - start) + "ms header: " + (header - init) + "ms content: " + (content - header)
  183. + "ms finalizing: " + (finalizing - content) + "ms ");
  184. ByteArrayOutputStream bout = new ByteArrayOutputStream();
  185. try {
  186. excelWorkbook.write(bout);
  187. } catch (IOException e) {
  188. throw new SaikuServiceException("Error creating excel export for query", e);
  189. }
  190. return bout.toByteArray();
  191. }
  192. private void checkRowLimit(int rowIndex) {
  193. if ((rowIndex + 1) > maxRows) {
  194. log.warn("Excel sheet is truncated, only outputting " + maxRows + " rows of " + (rowIndex + 1));
  195. }
  196. }
  197. private void addTotalsSummary(int startingRow) {
  198. int rowIndex = startingRow + rowsetBody.length + 2; // Lines offset after data, in order to add summary
  199. checkRowLimit(rowIndex);
  200. // Columns summary
  201. if (colScanTotals.keySet().size() > 0) {
  202. Row row = workbookSheet.createRow(rowIndex);
  203. Cell cell = row.createCell(0);
  204. cell.setCellStyle(lighterHeaderCellCS);
  205. cell.setCellValue("Columns");
  206. for (Integer colKey : colScanTotals.keySet()) {
  207. TotalAggregator[][] colAggregator = colScanTotals.get(colKey);
  208. if (colAggregator == null) continue;
  209. for (int x = 0; x < colAggregator.length; x++) {
  210. rowIndex++;
  211. checkRowLimit(rowIndex);
  212. Measure measure = this.table.getSelectedMeasures()[x];
  213. TotalAggregator agg = colAggregator[x][0];
  214. row = workbookSheet.createRow(rowIndex);
  215. // Measure name
  216. cell = row.createCell(0);
  217. cell.setCellStyle(lighterHeaderCellCS);
  218. cell.setCellValue(measure.getCaption() + ":");
  219. // Measure aggregator
  220. cell = row.createCell(1);
  221. cell.setCellStyle(basicCS);
  222. cell.setCellValue(agg.getClass().getSimpleName().substring(0, 3));
  223. }
  224. }
  225. }
  226. // Rows summary
  227. if (rowScanTotals.keySet().size() > 0) {
  228. rowIndex++;
  229. checkRowLimit(rowIndex);
  230. Row row = workbookSheet.createRow(rowIndex);
  231. Cell cell = row.createCell(0);
  232. cell.setCellStyle(lighterHeaderCellCS);
  233. cell.setCellValue("Rows");
  234. for (Integer rowKey : rowScanTotals.keySet()) {
  235. TotalAggregator[][] rowAggregator = rowScanTotals.get(rowKey);
  236. if (rowAggregator == null) continue;
  237. for (int x = 0; x < rowAggregator.length; x++) {
  238. for (int y = 0; y < this.table.getSelectedMeasures().length; y++) {
  239. rowIndex++;
  240. checkRowLimit(rowIndex);
  241. Measure measure = this.table.getSelectedMeasures()[y];
  242. TotalAggregator agg = rowAggregator[x][y];
  243. row = workbookSheet.createRow(rowIndex);
  244. // Measure name
  245. cell = row.createCell(0);
  246. cell.setCellStyle(lighterHeaderCellCS);
  247. cell.setCellValue(measure.getCaption() + ":");
  248. // Measure aggregator
  249. cell = row.createCell(1);
  250. cell.setCellStyle(basicCS);
  251. cell.setCellValue(agg.getClass().getSimpleName().substring(0, 3));
  252. }
  253. }
  254. }
  255. }
  256. }
  257. private void finalizeExcelSheet(int startRow) {
  258. boolean autoSize = (rowsetBody != null && rowsetBody.length > 0 && rowsetBody.length < 10000
  259. && rowsetHeader != null && rowsetHeader.length > 0 && rowsetHeader[0].length < 200);
  260. if (autoSize) {
  261. log.warn("Skipping auto-sizing columns, more than 10000 rows and/or 200 columns");
  262. }
  263. Long start = (new Date()).getTime();
  264. if (autoSize) {
  265. //Detect max column size
  266. int maxColumnsSize = rowsetBody[0].length;
  267. if (!colScanTotals.isEmpty()) {
  268. maxColumnsSize = Collections.max(colScanTotals.keySet()) + 1;
  269. }
  270. // Autosize columns
  271. for (int i = 0; i < maxColumns && i < maxColumnsSize; i++) {
  272. workbookSheet.autoSizeColumn(i);
  273. }
  274. }
  275. Long end = (new Date()).getTime();
  276. log.debug("Autosizing: " + (end - start) + "ms");
  277. // Freeze the header columns
  278. int headerWidth = rowsetHeader.length;
  279. workbookSheet.createFreezePane(0, startRow + headerWidth, 0, startRow + headerWidth);
  280. }
  281. private int initExcelSheet() {
  282. // Main Workbook Sheet
  283. if (StringUtils.isNotBlank(options.sheetName)) {
  284. workbookSheet = excelWorkbook.createSheet(this.sheetName);
  285. } else {
  286. workbookSheet = excelWorkbook.createSheet();
  287. }
  288. initSummarySheet();
  289. return 0;
  290. }
  291. private void initSummarySheet() {
  292. // Main Workbook Sheet
  293. Sheet summarySheet = excelWorkbook.createSheet("Summary page");
  294. int row = 1;
  295. Row sheetRow = summarySheet.createRow((int) row);
  296. Cell cell = sheetRow.createCell(0);
  297. String todayDate = (new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")).format(new Date());
  298. cell.setCellValue("Export date and time: " + todayDate);
  299. summarySheet.addMergedRegion(new CellRangeAddress(1, 1, 0, 2));
  300. row = row + 2;
  301. sheetRow = summarySheet.createRow((int) row);
  302. cell = sheetRow.createCell(0);
  303. cell.setCellValue("Dimension");
  304. cell = sheetRow.createCell(1);
  305. cell.setCellValue("Level");
  306. cell = sheetRow.createCell(2);
  307. cell.setCellValue("Filter Applied");
  308. row++;
  309. if (queryFilters != null) {
  310. for (ThinHierarchy item : queryFilters) {
  311. for (ThinLevel s : item.getLevels().values()) {
  312. for (ThinMember i : s.getSelection().getMembers()) {
  313. sheetRow = summarySheet.createRow((short) row);
  314. cell = sheetRow.createCell(0);
  315. cell.setCellValue(item.getCaption());
  316. cell = sheetRow.createCell(1);
  317. cell.setCellValue(s.getCaption());
  318. cell = sheetRow.createCell(2);
  319. cell.setCellValue(i.getCaption());
  320. row++;
  321. }
  322. }
  323. }
  324. }
  325. row += 2;
  326. int rowLength = (rowsetBody != null) ? rowsetBody.length : 0;
  327. int columnCount = (rowsetHeader != null && rowsetHeader.length > 0) ? rowsetHeader[0].length : 0;
  328. int headerLength = (rowsetHeader != null) ? rowsetHeader.length : 0;
  329. if (columnCount > maxColumns) {
  330. sheetRow = summarySheet.createRow((int) row);
  331. cell = sheetRow.createCell(0);
  332. cell.setCellValue("Excel sheet is truncated, only contains " + maxColumns + " columns of " + (columnCount));
  333. summarySheet.addMergedRegion(new CellRangeAddress(row, row, 0, 10));
  334. row++;
  335. }
  336. if ((headerLength + rowLength) > maxRows) {
  337. sheetRow = summarySheet.createRow((int) row);
  338. cell = sheetRow.createCell(0);
  339. cell.setCellValue(
  340. "Excel sheet is truncated, only contains " + maxRows + " rows of " + (headerLength + rowLength));
  341. summarySheet.addMergedRegion(new CellRangeAddress(row, row, 0, 10));
  342. row++;
  343. }
  344. row++;
  345. sheetRow = summarySheet.createRow((int) row);
  346. cell = sheetRow.createCell(0);
  347. cell.setCellValue(SaikuProperties.webExportExcelPoweredBy);
  348. summarySheet.addMergedRegion(new CellRangeAddress(row, row, 0, 10));
  349. // Autosize columns for summary sheet
  350. for (int i = 0; i < 5; i++) {
  351. summarySheet.autoSizeColumn(i);
  352. }
  353. }
  354. private void addExcelTableRows(int startingRow) {
  355. Row sheetRow = null;
  356. Cell cell = null;
  357. Map<Integer, String> tmpCellUniqueValueByColumn = new HashMap<>();
  358. Map<Integer, Map<Integer, Boolean>> mergeRowsByColumn = new HashMap<>();
  359. if ((startingRow + rowsetBody.length) > maxRows) {
  360. log.warn("Excel sheet is truncated, only outputting " + maxRows + " rows of "
  361. + (rowsetBody.length + startingRow));
  362. }
  363. if (rowsetBody.length > 0 && rowsetBody[0].length > maxColumns) {
  364. log.warn("Excel sheet is truncated, only outputting " + maxColumns + " columns of "
  365. + (rowsetBody[0].length));
  366. }
  367. int rowCount = startingRow;
  368. for (int x = 0; (x + startingRow) < maxRows && x < rowsetBody.length; x++) {
  369. int excelRowIndex = x + startingRow;
  370. sheetRow = workbookSheet.createRow(excelRowIndex);
  371. int column = 0;
  372. for (int y = 0; y < maxColumns && y < rowsetBody[x].length; y++) {
  373. cell = sheetRow.createCell(column);
  374. AbstractBaseCell baseCell = rowsetBody[x][y];
  375. //Detect merge cells
  376. findMergeCells(baseCell, excelRowIndex, y, mergeRowsByColumn, tmpCellUniqueValueByColumn);
  377. String value = baseCell.getFormattedValue();
  378. if (value == null && options.repeatValues) {
  379. // If the row cells has a null values it means the value is
  380. // repeated in the data internally
  381. // but not in the interface. To properly format the Excel
  382. // export file we need that value so we
  383. // get it from the same position in the prev row
  384. value = workbookSheet.getRow(sheetRow.getRowNum() - 1).getCell(column).getStringCellValue();
  385. }
  386. cell.setCellStyle(basicCS);
  387. cell.setCellValue(value);
  388. // Use rawNumber only is there is a formatString
  389. if (rowsetBody[x][y] instanceof DataCell) {
  390. DataCell dataCell = (DataCell) rowsetBody[x][y];
  391. String formatString = dataCell.getFormatString();
  392. if ((dataCell.getRawNumber() != null) && (formatString != null) && !formatString.trim().isEmpty()) {
  393. Number numberValue = dataCell.getRawNumber();
  394. cell.setCellValue(numberValue.doubleValue());
  395. if(numberValue.doubleValue() != 0.0 || !formatString.trim().equals("#,###")){//不等于0.0时 就设置样式
  396. applyCellFormatting(cell, dataCell);
  397. }else{
  398. cell.setCellStyle(number0CS);
  399. }
  400. }
  401. }
  402. //Set column sub totalstotals
  403. column = setColTotalAggregationCell(colScanTotals, sheetRow, x, column, true, false);
  404. //Set column grand totals
  405. if (y == rowsetBody[x].length - 1) {
  406. setColTotalAggregationCell(colScanTotals, sheetRow, x, column - 1, true, x == 0);
  407. }
  408. }
  409. // Set row sub totals
  410. startingRow = setRowTotalAggregationCell(rowScanTotals, startingRow, x, false);
  411. rowCount = startingRow + x;
  412. }
  413. //Set row grand totals
  414. setRowTotalAggregationCell(rowScanTotals, rowCount, 0, true);
  415. //Add merge cells
  416. addMergedRegions(mergeRowsByColumn);
  417. }
  418. private void scanRowAndColumnAggregations(List<TotalNode>[] rowTotalsLists, Map<Integer, TotalAggregator[][]> rowScanTotals, List<TotalNode>[] colTotalsLists, Map<Integer, TotalAggregator[][]> colScanTotals) {
  419. if (rowTotalsLists != null) {
  420. for (List<TotalNode> totalNodes : rowTotalsLists) {
  421. //Scan row totals
  422. scanAggregations(true, totalNodes, rowScanTotals);
  423. }
  424. }
  425. if (colTotalsLists != null) {
  426. for (List<TotalNode> totalNodes : colTotalsLists) {
  427. //Scan Columns grand totals
  428. scanAggregations(false, totalNodes, colScanTotals);
  429. }
  430. }
  431. }
  432. private void scanAggregations(boolean row, List<TotalNode> totalNodes, Map<Integer, TotalAggregator[][]> scanSums) {
  433. if (totalNodes != null && (!totalNodes.isEmpty())) {
  434. int index;
  435. if (row) {
  436. index = rowsetHeader.length - 2;
  437. } else {
  438. index = detectColumnStartIndex();
  439. }
  440. for (TotalNode n : totalNodes) {
  441. TotalAggregator[][] tg = n.getTotalGroups();
  442. if (tg.length > 0) {
  443. if (n.getSpan() > n.getWidth()) {
  444. index += n.getSpan();
  445. } else {
  446. index += n.getWidth();
  447. }
  448. index++;
  449. scanSums.put(index, tg);
  450. }
  451. }
  452. }
  453. }
  454. private int setRowTotalAggregationCell(Map<Integer, TotalAggregator[][]> scanTotals, int startIndex, int subIndex, boolean grandTotal) {
  455. if (!scanTotals.isEmpty()) {
  456. int row = subIndex + startIndex;
  457. TotalAggregator[][] aggregatorsTable = scanTotals.get(row);
  458. if (aggregatorsTable != null) {
  459. //Create totals row
  460. Row sheetRow = workbookSheet.createRow(row + 1);
  461. //Detect column start index
  462. int startColumnIndex = detectColumnStartIndex();
  463. if (grandTotal) {
  464. setGrandTotalLabel(sheetRow, startColumnIndex, false);
  465. }
  466. for (TotalAggregator[] aggregators : aggregatorsTable) {
  467. int column = startColumnIndex;
  468. for (TotalAggregator aggregator : aggregators) {
  469. //Calculate column sub total index
  470. column = setColTotalAggregationCell(colScanTotals, null, -1, column, false, false);
  471. //Create row totals cell
  472. Cell cell = sheetRow.createCell(column);
  473. String value = aggregator.getFormattedValue();
  474. cell.setCellValue(value);
  475. cell.setCellStyle(totalsCS);
  476. }
  477. }
  478. startIndex++;
  479. }
  480. }
  481. return startIndex;
  482. }
  483. /**
  484. * @return columns data start index
  485. */
  486. private int detectColumnStartIndex() {
  487. int index = 0;
  488. if (rowsetBody.length > 0) {
  489. for (AbstractBaseCell cell : rowsetBody[0]) {
  490. if (cell instanceof MemberCell) {
  491. index++;
  492. }
  493. }
  494. index--;
  495. }
  496. return index;
  497. }
  498. private int setColTotalAggregationCell(Map<Integer, TotalAggregator[][]> scanTotals, Row sheetRow, int x, int column, boolean setValue, boolean grandTotal) {
  499. column++;
  500. if (!scanTotals.isEmpty()) {
  501. TotalAggregator[][] aggregatorsTable = scanTotals.get(column);
  502. if (aggregatorsTable != null) {
  503. if (setValue) {
  504. if (grandTotal) {
  505. setGrandTotalLabel(sheetRow.getRowNum() - 1, column, true);
  506. }
  507. for (TotalAggregator[] aggregators : aggregatorsTable) {
  508. Cell cell = sheetRow.createCell(column);
  509. String value = aggregators[x].getFormattedValue();
  510. cell.setCellValue(value);
  511. cell.setCellStyle(totalsCS);
  512. column++;
  513. }
  514. }
  515. }
  516. }
  517. return column;
  518. }
  519. private void setGrandTotalLabel(int x, int y, boolean header) {
  520. Row sheetRow = workbookSheet.getRow(x);
  521. if (sheetRow != null) {
  522. setGrandTotalLabel(sheetRow, y, header);
  523. }
  524. }
  525. private void setGrandTotalLabel(Row sheetRow, int y, boolean header) {
  526. Cell cell = sheetRow.createCell(y);
  527. //TODO i18n
  528. String value = "Grand Total";
  529. if (header) {
  530. fillHeaderCell(sheetRow, value, y);
  531. } else {
  532. cell.setCellValue(value);
  533. cell.setCellStyle(basicCS);
  534. }
  535. }
  536. /**
  537. * Apply exact number format to excel Cell from its DataCell. Caller checks
  538. * the DataCell rawNumber and formatString are correct.
  539. *
  540. * @param cell The excel cell to apply formatting
  541. * @param dataCell The source
  542. */
  543. private void applyCellFormatting(Cell cell, DataCell dataCell) {
  544. /*
  545. * Previously, the CellStyles were being kept on a hash map for reuse,
  546. * but the key used was just the formatString (not considering the
  547. * colours), so, if many cells shared the same formatString but using
  548. * different colours, all those cells would have the last cell colour.
  549. */
  550. String formatString = dataCell.getFormatString();
  551. CellStyle numberCSClone = excelWorkbook.createCellStyle();
  552. numberCSClone.cloneStyleFrom(numberCS);
  553. try {
  554. formatString = FormatUtil.getFormatString(formatString);
  555. DataFormat fmt = excelWorkbook.createDataFormat();
  556. short dataFormat = fmt.getFormat(formatString);
  557. numberCSClone.setDataFormat(dataFormat);
  558. } catch (Exception ex) {
  559. }
  560. // Check for cell background
  561. Map<String, String> properties = dataCell.getProperties();
  562. // Just style the cell if it contains a value
  563. if (dataCell.getRawNumber() != null && properties.containsKey("style")) {
  564. String colorCode = properties.get("style");
  565. short colorCodeIndex = getColorFromCustomPalette(colorCode);
  566. if (colorCodeIndex != -1) {
  567. numberCSClone.setFillForegroundColor(colorCodeIndex);
  568. numberCSClone.setFillPattern(FillPatternType.SOLID_FOREGROUND);
  569. } else if (customColorsPalette == null) {
  570. try {
  571. if (cssColorCodesProperties != null && cssColorCodesProperties.containsKey(colorCode)) {
  572. colorCode = cssColorCodesProperties.getProperty(colorCode);
  573. }
  574. int redCode = Integer.parseInt(colorCode.substring(1, 3), 16);
  575. int greenCode = Integer.parseInt(colorCode.substring(3, 5), 16);
  576. int blueCode = Integer.parseInt(colorCode.substring(5, 7), 16);
  577. numberCSClone.setFillPattern(FillPatternType.SOLID_FOREGROUND);
  578. ((XSSFCellStyle) numberCSClone).setFillForegroundColor(
  579. new XSSFColor(new java.awt.Color(redCode, greenCode, blueCode)));
  580. ((XSSFCellStyle) numberCSClone).setFillBackgroundColor(
  581. new XSSFColor(new java.awt.Color(redCode, greenCode, blueCode)));
  582. } catch (Exception e) {
  583. // we tried to set the color, no luck, lets continue
  584. // without
  585. }
  586. }
  587. } else {
  588. numberCSClone.setFillForegroundColor(numberCS.getFillForegroundColor());
  589. numberCSClone.setFillBackgroundColor(numberCS.getFillBackgroundColor());
  590. }
  591. cell.setCellStyle(numberCSClone);
  592. }
  593. private short getColorFromCustomPalette(String style) {
  594. short returnedColorIndex = -1;
  595. InputStream is = null;
  596. if (colorCodesMap.containsKey(style)) {
  597. returnedColorIndex = colorCodesMap.get(style).shortValue();
  598. } else {
  599. try {
  600. if (cssColorCodesProperties == null) {
  601. is = getClass().getResourceAsStream(CSS_COLORS_CODE_PROPERTIES);
  602. if (is != null) {
  603. cssColorCodesProperties = new Properties();
  604. cssColorCodesProperties.load(is);
  605. }
  606. }
  607. String colorCode = cssColorCodesProperties.getProperty(style);
  608. if (colorCode != null) {
  609. try {
  610. int redCode = Integer.parseInt(colorCode.substring(1, 3), 16);
  611. int greenCode = Integer.parseInt(colorCode.substring(3, 5), 16);
  612. int blueCode = Integer.parseInt(colorCode.substring(5, 7), 16);
  613. if (customColorsPalette != null) {
  614. customColorsPalette.setColorAtIndex((byte) nextAvailableColorCode, (byte) redCode,
  615. (byte) greenCode, (byte) blueCode);
  616. returnedColorIndex = customColorsPalette.getColor(nextAvailableColorCode).getIndex();
  617. colorCodesMap.put(style, (int) returnedColorIndex);
  618. } else {
  619. return -1;
  620. }
  621. nextAvailableColorCode++;
  622. } catch (Exception e) {
  623. // we tried to set the color, no luck, lets continue
  624. // without
  625. return -1;
  626. }
  627. }
  628. } catch (IOException e) {
  629. log.error("IO Exception", e);
  630. } finally {
  631. try {
  632. if (is != null)
  633. is.close();
  634. } catch (IOException e) {
  635. log.error("IO Exception", e);
  636. }
  637. }
  638. }
  639. return returnedColorIndex; // To change body of created methods use File
  640. // | Settings | File Templates.
  641. }
  642. private int buildExcelTableHeader(int startRow) {
  643. Row sheetRow = null;
  644. int x = 0;
  645. int y = 0;
  646. int startSameFromPos = 0;
  647. int mergedCellsWidth = 0;
  648. boolean isLastHeaderRow = false;
  649. boolean isLastColumn = false;
  650. String nextHeader = EMPTY_STRING;
  651. String currentHeader = EMPTY_STRING;
  652. ArrayList<ExcelMergedRegionItemConfig> mergedItemsConfig = new ArrayList<>();
  653. for (x = 0; x < rowsetHeader.length; x++) {
  654. sheetRow = workbookSheet.createRow((int) x + startRow);
  655. nextHeader = EMPTY_STRING;
  656. isLastColumn = false;
  657. startSameFromPos = 0;
  658. mergedCellsWidth = 0;
  659. if (x + 1 == rowsetHeader.length) {
  660. isLastHeaderRow = true;
  661. }
  662. int column = 0;
  663. for (y = 0; y < maxColumns && y < rowsetHeader[x].length; y++) {
  664. currentHeader = rowsetHeader[x][y].getFormattedValue();
  665. if (currentHeader != null) {
  666. if (rowsetHeader[x].length == y + 1) {
  667. isLastColumn = true;
  668. } else {
  669. nextHeader = rowsetHeader[x][y + 1].getFormattedValue();
  670. }
  671. manageColumnHeaderDisplay(sheetRow, x, column, currentHeader);
  672. if (!isLastHeaderRow) {
  673. if (nextHeader != null && !nextHeader.equals(currentHeader) || isLastColumn) {
  674. manageCellsMerge(column, x + startRow, mergedCellsWidth + 1, startSameFromPos,
  675. mergedItemsConfig);
  676. startSameFromPos = column + 1;
  677. mergedCellsWidth = 0;
  678. } else if (nextHeader != null && nextHeader.equals(currentHeader)) {
  679. mergedCellsWidth++;
  680. }
  681. }
  682. } else {
  683. startSameFromPos++;
  684. }
  685. //Set sub total column space
  686. int nextColumn = setColTotalAggregationCell(colScanTotals, sheetRow, x, column, false, false);
  687. if (column != nextColumn - 1) {
  688. startSameFromPos++;
  689. }
  690. column = nextColumn;
  691. }
  692. // Manage the merge condition on exit from columns scan
  693. if (!isLastHeaderRow)
  694. manageCellsMerge(y - 1, x, mergedCellsWidth + 1, startSameFromPos, mergedItemsConfig);
  695. }
  696. if (topLeftCornerHeight > 0 && topLeftCornerWidth > 0) {
  697. workbookSheet.addMergedRegion(
  698. new CellRangeAddress(startRow, startRow + topLeftCornerHeight - 1, 0, topLeftCornerWidth - 1));
  699. }
  700. if (mergedItemsConfig.size() > 0) {
  701. for (ExcelMergedRegionItemConfig item : mergedItemsConfig) {
  702. int lastCol = item.getStartX() + item.getWidth() - 1;
  703. lastCol = lastCol >= maxColumns ? maxColumns - 1 : lastCol;
  704. workbookSheet.addMergedRegion(new CellRangeAddress(item.getStartY(),
  705. item.getStartY() + item.getHeight(), item.getStartX(), lastCol));
  706. }
  707. }
  708. return x + startRow;
  709. }
  710. private void manageColumnHeaderDisplay(Row sheetRow, int x, int y, String currentHeader) {
  711. if (topLeftCornerHeight > 0 && x >= topLeftCornerHeight) {
  712. fillHeaderCell(sheetRow, currentHeader, y);
  713. } else if ((topLeftCornerHeight > 0 && x < topLeftCornerHeight)
  714. && (topLeftCornerWidth > 0 && y >= topLeftCornerWidth)) {
  715. fillHeaderCell(sheetRow, currentHeader, y);
  716. } else if (topLeftCornerHeight == 0 && topLeftCornerWidth == 0)
  717. fillHeaderCell(sheetRow, currentHeader, y);
  718. }
  719. private void manageCellsMerge(int rowPos, int colPos, int width, int startSameFromPos,
  720. ArrayList<ExcelMergedRegionItemConfig> mergedItemsConfig) {
  721. ExcelMergedRegionItemConfig foundItem = null;
  722. boolean itemGetFromList = false;
  723. if (width == 1)
  724. return;
  725. for (ExcelMergedRegionItemConfig item : mergedItemsConfig) {
  726. if (item.getStartY() == colPos && item.getStartX() == rowPos) {
  727. foundItem = item;
  728. itemGetFromList = true;
  729. }
  730. }
  731. if (foundItem == null)
  732. foundItem = new ExcelMergedRegionItemConfig();
  733. foundItem.setHeight(0);
  734. foundItem.setWidth(width);
  735. foundItem.setStartX(startSameFromPos);
  736. foundItem.setStartY(colPos);
  737. if (mergedItemsConfig.isEmpty() || !itemGetFromList)
  738. mergedItemsConfig.add(foundItem);
  739. }
  740. private void fillHeaderCell(Row sheetRow, String formattedValue, int y) {
  741. Cell cell = sheetRow.createCell(y);
  742. cell.setCellValue(formattedValue);
  743. cell.setCellStyle(lighterHeaderCellCS);
  744. }
  745. /**
  746. * Find the width in cells of the top left corner of the table
  747. *
  748. * @return
  749. */
  750. private int findTopLeftCornerWidth() {
  751. int width = 0;
  752. int x = 0;
  753. boolean exit = (rowsetHeader.length < 1 || rowsetHeader[0][0].getRawValue() != null);
  754. String cellValue = null;
  755. for (x = 0; (!exit && rowsetHeader[0].length > x); x++) {
  756. cellValue = rowsetHeader[0][x].getRawValue();
  757. if (cellValue == null) {
  758. width = x + 1;
  759. } else {
  760. exit = true;
  761. }
  762. }
  763. return width;
  764. }
  765. /**
  766. * Find the height in cells of the top left corner of the table
  767. *
  768. * @return
  769. */
  770. private int findTopLeftCornerHeight() {
  771. return rowsetHeader.length > 0 ? rowsetHeader.length - 1 : 0;
  772. }
  773. /**
  774. * @param mergeRowsByColumn merged indexes
  775. */
  776. private void addMergedRegions(Map<Integer, Map<Integer, Boolean>> mergeRowsByColumn) {
  777. if (mergeRowsByColumn != null) {
  778. for (Map.Entry<Integer, Map<Integer, Boolean>> e : mergeRowsByColumn.entrySet()) {
  779. int col = e.getKey();
  780. Map<Integer, Boolean> rows = e.getValue();
  781. if (rows != null) {
  782. int mergeCount = 1;
  783. for (Map.Entry<Integer, Boolean> rowEntry : rows.entrySet()) {
  784. int row = rowEntry.getKey();
  785. boolean current = rowEntry.getValue();
  786. Boolean next = rows.get(rowEntry.getKey() + 1);
  787. if (current) {
  788. if (next == null || !next) {
  789. workbookSheet.addMergedRegion(new CellRangeAddress(row - mergeCount, row, col, col));
  790. }
  791. mergeCount++;
  792. } else {
  793. mergeCount = 1;
  794. }
  795. }
  796. }
  797. }
  798. }
  799. }
  800. /**
  801. * @param baseCell current cell
  802. * @param excelRowIndex row index
  803. * @param y column
  804. * @param mergeRowsByColumn merge indexes store
  805. * @param tmpCellUniqueValueByColumn tmp map to compare previews value(max possible value = columns size)
  806. */
  807. private void findMergeCells(AbstractBaseCell baseCell,
  808. int excelRowIndex,
  809. int y,
  810. Map<Integer, Map<Integer, Boolean>> mergeRowsByColumn,
  811. Map<Integer, String> tmpCellUniqueValueByColumn) {
  812. if (baseCell instanceof MemberCell) {
  813. MemberCell memberCell = (MemberCell) baseCell;
  814. Map<Integer, Boolean> rowMerge = mergeRowsByColumn.get(y);
  815. if (rowMerge == null) {
  816. rowMerge = new TreeMap<>();
  817. mergeRowsByColumn.put(y, rowMerge);
  818. }
  819. //Compare preview and current cells
  820. String previousValue = tmpCellUniqueValueByColumn.get(y);
  821. Map<Integer, Boolean> previousColumn = mergeRowsByColumn.get(y - 1);
  822. boolean merge = previousValue != null && previousValue.equals(memberCell.getUniqueName());
  823. if (previousColumn != null) {
  824. Boolean previewColumnCellmergeValue = previousColumn.get(excelRowIndex);
  825. if ((previewColumnCellmergeValue != null) && (!previewColumnCellmergeValue) && merge) {
  826. merge = false;
  827. }
  828. }
  829. rowMerge.put(excelRowIndex, merge);
  830. tmpCellUniqueValueByColumn.put(y, memberCell.getUniqueName());
  831. }
  832. }
  833. }

  

FlattenedCellSetFormatter.java

  1. /*
  2. * Copyright 2012 OSBI Ltd
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package org.saiku.olap.util.formatter;
  17. import java.text.DecimalFormat;
  18. import java.text.DecimalFormatSymbols;
  19. import java.util.AbstractList;
  20. import java.util.ArrayList;
  21. import java.util.Collections;
  22. import java.util.HashMap;
  23. import java.util.Iterator;
  24. import java.util.List;
  25. import java.util.Map;
  26. import org.olap4j.Cell;
  27. import org.olap4j.CellSet;
  28. import org.olap4j.CellSetAxis;
  29. import org.olap4j.Position;
  30. import org.olap4j.impl.CoordinateIterator;
  31. import org.olap4j.impl.Olap4jUtil;
  32. import org.olap4j.metadata.Level;
  33. import org.olap4j.metadata.Member;
  34. import org.olap4j.metadata.Property;
  35. import org.saiku.olap.dto.resultset.DataCell;
  36. import org.saiku.olap.dto.resultset.Matrix;
  37. import org.saiku.olap.dto.resultset.MemberCell;
  38. import org.saiku.olap.util.SaikuProperties;
  39. public class FlattenedCellSetFormatter implements ICellSetFormatter {
  40. /**
  41. * Description of an axis.
  42. */
  43. private static class AxisInfo {
  44. final List<AxisOrdinalInfo> ordinalInfos;
  45. /**
  46. * Creates an AxisInfo.
  47. *
  48. * @param ordinalCount Number of hierarchies on this axis
  49. */
  50. AxisInfo(final int ordinalCount) {
  51. ordinalInfos = new ArrayList<>(ordinalCount);
  52. // For each index from 0 to the number of hierarchies ...
  53. for (int i = 0; i < ordinalCount; i++) {
  54. // Associate an AxisOrdinalInfo instance
  55. ordinalInfos.add(new AxisOrdinalInfo());
  56. }
  57. }
  58. /**
  59. * Returns the number of matrix columns required by this axis. The sum of
  60. * the width of the hierarchies on this axis.
  61. *
  62. * @return Width of axis
  63. */
  64. public int getWidth() {
  65. int width = 0;
  66. for (final AxisOrdinalInfo info : ordinalInfos) {
  67. width += info.getWidth();
  68. }
  69. return width;
  70. }
  71. }
  72. /**
  73. * Description of a particular hierarchy mapped to an axis.
  74. */
  75. private static class AxisOrdinalInfo {
  76. private final List<Integer> depths = new ArrayList<>();
  77. private final Map<Integer,Level> depthLevel = new HashMap<>();
  78. public int getWidth() {
  79. return depths.size();
  80. }
  81. public List<Integer> getDepths() {
  82. return depths;
  83. }
  84. public Level getLevel(Integer depth) {
  85. return depthLevel.get(depth);
  86. }
  87. public void addLevel(Integer depth, Level level) {
  88. depthLevel.put(depth, level);
  89. }
  90. }
  91. /**
  92. * Returns an iterator over cells in a result.
  93. */
  94. private static Iterable<Cell> cellIter(final int[] pageCoords, final CellSet cellSet) {
  95. return new Iterable<Cell>() {
  96. public Iterator<Cell> iterator() {
  97. final int[] axisDimensions = new int[cellSet.getAxes().size() - pageCoords.length];
  98. assert pageCoords.length <= axisDimensions.length;
  99. for (int i = 0; i < axisDimensions.length; i++) {
  100. final CellSetAxis axis = cellSet.getAxes().get(i);
  101. axisDimensions[i] = axis.getPositions().size();
  102. }
  103. final CoordinateIterator coordIter = new CoordinateIterator(axisDimensions, true);
  104. return new Iterator<Cell>() {
  105. public boolean hasNext() {
  106. return coordIter.hasNext();
  107. }
  108. public Cell next() {
  109. final int[] ints = coordIter.next();
  110. final AbstractList<Integer> intList = new AbstractList<Integer>() {
  111. @Override
  112. public Integer get(final int index) {
  113. return index < ints.length ? ints[index] : pageCoords[index - ints.length];
  114. }
  115. @Override
  116. public int size() {
  117. return pageCoords.length + ints.length;
  118. }
  119. };
  120. return cellSet.getCell(intList);
  121. }
  122. public void remove() {
  123. throw new UnsupportedOperationException();
  124. }
  125. };
  126. }
  127. };
  128. }
  129. private Matrix matrix;
  130. private final List<Integer> ignorex = new ArrayList<>();
  131. private final List<Integer> ignorey = new ArrayList<>();
  132. /**
  133. * This is the main method of a cellset formatter, it receives a cellset as
  134. * input and converts it on a matrix, a bidimensional representation of query
  135. * values, arranged in a xy cartesian coordinate system.
  136. * @param cellSet
  137. * @return
  138. */
  139. public Matrix format(final CellSet cellSet) {
  140. // Compute how many rows are required to display the columns axis.
  141. final CellSetAxis columnsAxis;
  142. // If the axes are not empty, the first one is the column axis
  143. if (cellSet.getAxes().size() > 0) {
  144. // As a convention, the columns axis is associated with the index 0
  145. columnsAxis = cellSet.getAxes().get(0);
  146. } else {
  147. columnsAxis = null;
  148. }
  149. final AxisInfo columnsAxisInfo = computeAxisInfo(columnsAxis);
  150. // Compute how many columns are required to display the rows axis.
  151. final CellSetAxis rowsAxis;
  152. // If there are more than one axis, the second one is the rows axis
  153. if (cellSet.getAxes().size() > 1) {
  154. // As a convention, the rows axis is associated with the index 1
  155. rowsAxis = cellSet.getAxes().get(1);
  156. } else {
  157. rowsAxis = null;
  158. }
  159. final AxisInfo rowsAxisInfo = computeAxisInfo(rowsAxis);
  160. if (cellSet.getAxes().size() > 2) {
  161. final int[] dimensions = new int[cellSet.getAxes().size() - 2];
  162. for (int i = 2; i < cellSet.getAxes().size(); i++) {
  163. final CellSetAxis cellSetAxis = cellSet.getAxes().get(i);
  164. dimensions[i - 2] = cellSetAxis.getPositions().size();
  165. }
  166. for (final int[] pageCoords : CoordinateIterator.iterate(dimensions)) {
  167. matrix = formatPage(cellSet, pageCoords, columnsAxis, columnsAxisInfo, rowsAxis, rowsAxisInfo);
  168. }
  169. } else {
  170. matrix = formatPage(cellSet, new int[] {}, columnsAxis, columnsAxisInfo, rowsAxis, rowsAxisInfo);
  171. }
  172. return matrix;
  173. }
  174. /**
  175. * Computes a description of an axis. Each axis is composed by many positions,
  176. * each position is then composed by many members. A member is a 'point' on a
  177. * dimension of a cube. Every member belongs to a Level of a Hierarchy. The
  178. * member's depth is its distance to the root member.
  179. *
  180. * @param axis Axis
  181. * @return Description of axis
  182. */
  183. private AxisInfo computeAxisInfo(final CellSetAxis axis) {
  184. if (axis == null) {
  185. return new AxisInfo(0);
  186. }
  187. // An axis info is created by informing the number of hierarchies of axis
  188. final AxisInfo axisInfo = new AxisInfo(axis.getAxisMetaData().getHierarchies().size());
  189. int p = -1;
  190. // For each axis position
  191. for (final Position position : axis.getPositions()) {
  192. ++p;
  193. int k = -1;
  194. // For each member of the axis
  195. for (final Member member : position.getMembers()) {
  196. ++k;
  197. // Fetch the AxisOrdinalInfo instance of the position index k
  198. final AxisOrdinalInfo axisOrdinalInfo = axisInfo.ordinalInfos.get(k);
  199. // We avoid duplicating information for members with the same depth
  200. if (!axisOrdinalInfo.getDepths().contains(member.getDepth())) {
  201. axisOrdinalInfo.getDepths().add(member.getDepth());
  202. // For each depth of the hiearchy, add its level
  203. axisOrdinalInfo.addLevel(member.getDepth(), member.getLevel());
  204. Collections.sort(axisOrdinalInfo.depths);
  205. }
  206. }
  207. }
  208. // The axisInfo object, contains a collection of the hiearchy's levels
  209. // sorted by their depths.
  210. return axisInfo;
  211. }
  212. /**
  213. * Formats a two-dimensional page.
  214. *
  215. * @param cellSet
  216. * Cell set
  217. * @param pageCoords
  218. * Print writer
  219. * @param pageCoords
  220. * Coordinates of page [page, chapter, section, ...]
  221. * @param columnsAxis
  222. * Columns axis
  223. * @param columnsAxisInfo
  224. * Description of columns axis
  225. * @param rowsAxis
  226. * Rows axis
  227. * @param rowsAxisInfo
  228. * Description of rows axis
  229. */
  230. private Matrix formatPage(final CellSet cellSet, final int[] pageCoords, final CellSetAxis columnsAxis,
  231. final AxisInfo columnsAxisInfo, final CellSetAxis rowsAxis, final AxisInfo rowsAxisInfo) {
  232. // Figure out the dimensions of the blank rectangle in the top left
  233. // corner.
  234. final int yOffset = columnsAxisInfo.getWidth();
  235. final int xOffsset = rowsAxisInfo.getWidth();
  236. // Populate a string matrix
  237. final Matrix matrix = new Matrix(xOffsset + (columnsAxis == null ? 1 : columnsAxis.getPositions().size()),
  238. yOffset + (rowsAxis == null ? 1 : rowsAxis.getPositions().size()));
  239. // Populate corner
  240. List<Level> levels = new ArrayList<>();
  241. if (rowsAxis != null && rowsAxis.getPositions().size() > 0) {
  242. // We assume that every position contains members with same levels,
  243. // so, we just need the first position to retrieve this information.
  244. Position p = rowsAxis.getPositions().get(0);
  245. for (int m = 0; m < p.getMembers().size(); m++) {
  246. AxisOrdinalInfo a = rowsAxisInfo.ordinalInfos.get(m);
  247. // For each member's depth of the first position, add its level
  248. for (Integer depth : a.getDepths()) {
  249. levels.add(a.getLevel(depth));
  250. }
  251. }
  252. for (int x = 0; x < xOffsset; x++) {
  253. Level xLevel = levels.get(x);
  254. String s = xLevel.getCaption();
  255. for (int y = 0; y < yOffset; y++) {
  256. final MemberCell memberInfo = new MemberCell(false, x > 0);
  257. if (y == yOffset-1) {
  258. memberInfo.setRawValue(s);
  259. memberInfo.setFormattedValue(s);
  260. memberInfo.setProperty("__headertype", "row_header_header");
  261. memberInfo.setProperty("levelindex", "" + levels.indexOf(xLevel));
  262. memberInfo.setHierarchy(xLevel.getHierarchy().getUniqueName());
  263. memberInfo.setParentDimension(xLevel.getDimension().getName());
  264. memberInfo.setLevel(xLevel.getUniqueName());
  265. }
  266. matrix.set(x, y, memberInfo);
  267. }
  268. }
  269. }
  270. // Populate matrix with cells representing axes
  271. populateAxis(matrix, columnsAxis, columnsAxisInfo, true, xOffsset);
  272. populateAxis(matrix, rowsAxis, rowsAxisInfo, false, yOffset);
  273. // TODO - why did we do this in the first place??? HERE BE DRAGONS
  274. // int headerwidth = matrix.getMatrixWidth();
  275. // if (headerwidth > 2) {
  276. // for(int yy=matrix.getMatrixHeight(); yy > matrix.getOffset() ; yy--) {
  277. // for(int xx=0; xx < headerwidth-1;xx++) {
  278. // if (matrix.get(xx,yy-1) != null && matrix.get(xx,yy) != null && matrix.get(xx,yy-1).getRawValue() != null
  279. // && matrix.get(xx,yy-1).getRawValue().equals(matrix.get(xx, yy).getRawValue()))
  280. // {
  281. // matrix.set(xx, yy, new MemberCell());
  282. // }
  283. // else {
  284. // break;
  285. // }
  286. // }
  287. // }
  288. // }
  289. // Populate cell values
  290. int newyOffset = yOffset;
  291. int newxOffset = xOffsset;
  292. List<Integer> donex = new ArrayList<>();
  293. List<Integer> doney = new ArrayList<>();
  294. for (final Cell cell : cellIter(pageCoords, cellSet)) {
  295. final List<Integer> coordList = cell.getCoordinateList();
  296. int y = newyOffset;
  297. int x = newxOffset;
  298. if (coordList.size() > 0) {
  299. if (coordList.get(0) == 0) {
  300. newxOffset = xOffsset;
  301. donex = new ArrayList<>();
  302. }
  303. x = newxOffset;
  304. if (coordList.size() > 0)
  305. x += coordList.get(0);
  306. y = newyOffset;
  307. if (coordList.size() > 1)
  308. y += coordList.get(1);
  309. boolean stop = false;
  310. if (coordList.size() > 0 && ignorex.contains(coordList.get(0))) {
  311. if (!donex.contains(coordList.get(0))) {
  312. newxOffset--;
  313. donex.add(coordList.get(0));
  314. }
  315. stop = true;
  316. }
  317. if (coordList.size() > 1 && ignorey.contains(coordList.get(1))) {
  318. if (!doney.contains(coordList.get(1))) {
  319. newyOffset--;
  320. doney.add(coordList.get(1));
  321. }
  322. stop = true;
  323. }
  324. if (stop) {
  325. continue;
  326. }
  327. }
  328. final DataCell cellInfo = new DataCell(true, false, coordList);
  329. cellInfo.setCoordinates(cell.getCoordinateList());
  330. if (cell.getValue() != null) {
  331. try {
  332. cellInfo.setRawNumber(cell.getDoubleValue());
  333. } catch (Exception e1) {
  334. }
  335. }
  336. String cellValue = cell.getFormattedValue(); // First try to get a
  337. // formatted value
  338. if (cellValue == null || cellValue.equals("null")) { //$NON-NLS-1$
  339. cellValue =""; //$NON-NLS-1$
  340. }
  341. //0值处理 为"null"的时候 ,cellValue.startsWith("| |style=")表示原值为0,直接让其为0
  342. if (cellValue.startsWith("| |style=")) { //$NON-NLS-1$
  343. cellValue ="0";
  344. cellInfo.setRawValue(cellValue);
  345. }
  346. if ( cellValue.length() < 1) {
  347. final Object value = cell.getValue();
  348. if (value == null || value.equals("null")) //$NON-NLS-1$
  349. cellValue = ""; //$NON-NLS-1$
  350. else {
  351. try {
  352. // TODO this needs to become query / execution specific
  353. DecimalFormat myFormatter = new DecimalFormat(SaikuProperties.formatDefautNumberFormat); //$NON-NLS-1$
  354. DecimalFormatSymbols dfs = new DecimalFormatSymbols(SaikuProperties.locale);
  355. myFormatter.setDecimalFormatSymbols(dfs);
  356. cellValue = myFormatter.format(cell.getValue());
  357. }
  358. catch (Exception e) {
  359. // TODO: handle exception
  360. }
  361. }
  362. // the raw value
  363. }
  364. // Format string is relevant for Excel export
  365. // xmla cells can throw an error on this
  366. try {
  367. String formatString = (String) cell.getPropertyValue(Property.StandardCellProperty.FORMAT_STRING);
  368. if (formatString != null && !formatString.startsWith("|")) {
  369. cellInfo.setFormatString(formatString);
  370. } else {
  371. formatString = formatString.substring(1, formatString.length());
  372. cellInfo.setFormatString(formatString.substring(0, formatString.indexOf("|")));
  373. }
  374. } catch (Exception e) {
  375. // we tried
  376. }
  377. Map<String, String> cellProperties = new HashMap<>();
  378. String val = Olap4jUtil.parseFormattedCellValue(cellValue, cellProperties);
  379. //注釋掉properties style=white設置,防止導出的excel 指標信息無線條
  380. /*if (!cellProperties.isEmpty()) {
  381. cellInfo.setProperties(cellProperties);
  382. }*/
  383. cellInfo.setFormattedValue(val);
  384. matrix.set(x, y, cellInfo);
  385. }
  386. return matrix;
  387. }
  388. /**
  389. * Populates cells in the matrix corresponding to a particular axis.
  390. *
  391. * @param matrix
  392. * Matrix to populate
  393. * @param axis
  394. * Axis
  395. * @param axisInfo
  396. * Description of axis
  397. * @param isColumns
  398. * True if columns, false if rows
  399. * @param oldoffset
  400. * Ordinal of first cell to populate in matrix
  401. */
  402. private void populateAxis(final Matrix matrix, final CellSetAxis axis, final AxisInfo axisInfo,
  403. final boolean isColumns, final int oldoffset) {
  404. if (axis == null) {
  405. return;
  406. }
  407. int offset = oldoffset;
  408. final Member[] prevMembers = new Member[axisInfo.getWidth()];
  409. final MemberCell[] prevMemberInfo = new MemberCell[axisInfo.getWidth()];
  410. final Member[] members = new Member[axisInfo.getWidth()];
  411. // For each axis' position
  412. for (int i = 0; i < axis.getPositions().size(); i++) {
  413. final int x = offset + i;
  414. final Position position = axis.getPositions().get(i);
  415. int yOffset = 0;
  416. final List<Member> memberList = position.getMembers();
  417. boolean stop = false;
  418. // For each position's member
  419. for (int j = 0; j < memberList.size(); j++) {
  420. Member member = memberList.get(j);
  421. final AxisOrdinalInfo ordinalInfo = axisInfo.ordinalInfos.get(j);
  422. List<Integer> depths = ordinalInfo.depths;
  423. Collections.sort(depths);
  424. // If it is not the last member (the one with the highest depth)
  425. if (member.getDepth() < Collections.max(depths)) {
  426. stop = true;
  427. if (isColumns) {
  428. ignorex.add(i);
  429. } else {
  430. ignorey.add(i);
  431. }
  432. break;
  433. }
  434. if (ordinalInfo.getDepths().size() > 0 && member.getDepth() < ordinalInfo.getDepths().get(0)) {
  435. break;
  436. }
  437. // It stores each position's member in members array sorted by its depth
  438. final int y = yOffset + ordinalInfo.depths.indexOf(member.getDepth());
  439. members[y] = member;
  440. yOffset += ordinalInfo.getWidth();
  441. }
  442. if (stop) {
  443. offset--;
  444. continue;
  445. }
  446. boolean expanded = false;
  447. boolean same = true;
  448. for (int y = 0; y < members.length; y++) {
  449. final MemberCell memberInfo = new MemberCell();
  450. final Member member = members[y];
  451. // The index of the member on its position
  452. int index = memberList.indexOf(member);
  453. if (index >= 0) {
  454. final AxisOrdinalInfo ordinalInfo = axisInfo.ordinalInfos.get(index);
  455. int depth_i = ordinalInfo.getDepths().indexOf(member.getDepth());
  456. if (depth_i > 0) {
  457. expanded = true;
  458. }
  459. }
  460. memberInfo.setExpanded(expanded);
  461. same = same && i > 0 && Olap4jUtil.equal(prevMembers[y], member);
  462. if (member != null) {
  463. if (x - 1 == offset)
  464. memberInfo.setLastRow(true);
  465. matrix.setOffset(oldoffset);
  466. memberInfo.setRawValue(member.getUniqueName());
  467. memberInfo.setFormattedValue(member.getCaption()); // First try to get a formatted value
  468. memberInfo.setParentDimension(member.getDimension().getName());
  469. memberInfo.setUniquename(member.getUniqueName());
  470. memberInfo.setHierarchy(member.getHierarchy().getUniqueName());
  471. memberInfo.setLevel(member.getLevel().getUniqueName());
  472. // try {
  473. // memberInfo.setChildMemberCount(member.getChildMemberCount());
  474. // } catch (OlapException e) {
  475. // e.printStackTrace();
  476. // throw new RuntimeException(e);
  477. // }
  478. // NamedList<Property> values = member.getLevel().getProperties();
  479. // for(int j=0; j<values.size();j++){
  480. // String val;
  481. // try {
  482. // val = member.getPropertyFormattedValue(values.get(j));
  483. // } catch (OlapException e) {
  484. // e.printStackTrace();
  485. // throw new RuntimeException(e);
  486. // }
  487. // memberInfo.setProperty(values.get(j).getCaption(), val);
  488. // }
  489. // if (y > 0) {
  490. // for (int previ = y-1; previ >= 0;previ--) {
  491. // if(prevMembers[previ] != null) {
  492. // memberInfo.setRightOf(prevMemberInfo[previ]);
  493. // memberInfo.setRightOfDimension(prevMembers[previ].getDimension().getName());
  494. // previ = -1;
  495. // }
  496. // }
  497. // }
  498. // if (member.getParentMember() != null)
  499. // memberInfo.setParentMember(member.getParentMember().getUniqueName());
  500. } else {
  501. memberInfo.setRawValue(null);
  502. memberInfo.setFormattedValue(null);
  503. memberInfo.setParentDimension(null);
  504. }
  505. if (isColumns) {
  506. memberInfo.setRight(false);
  507. memberInfo.setSameAsPrev(same);
  508. if (member != null)
  509. memberInfo.setParentDimension(member.getDimension().getName());
  510. matrix.set(x, y, memberInfo);
  511. } else {
  512. memberInfo.setRight(false);
  513. memberInfo.setSameAsPrev(false);
  514. matrix.set(y, x, memberInfo);
  515. }
  516. int x_parent = isColumns ? x : y-1;
  517. int y_parent = isColumns ? y-1 : x;
  518. if (index >= 0) {
  519. final AxisOrdinalInfo ordinalInfo = axisInfo.ordinalInfos.get(index);
  520. int depth_i = ordinalInfo.getDepths().indexOf(member.getDepth());
  521. while (depth_i > 0) {
  522. depth_i--;
  523. int parentDepth = (ordinalInfo.getDepths().get(depth_i));
  524. Member parent = member.getParentMember();
  525. while (parent != null && parent.getDepth() > parentDepth) {
  526. parent = parent.getParentMember();
  527. }
  528. final MemberCell pInfo = new MemberCell();
  529. if (parent != null) {
  530. pInfo.setRawValue(parent.getUniqueName());
  531. pInfo.setFormattedValue(parent.getCaption()); // First try to get a formatted value
  532. pInfo.setParentDimension(parent.getDimension().getName());
  533. pInfo.setHierarchy(parent.getHierarchy().getUniqueName());
  534. pInfo.setUniquename(parent.getUniqueName());
  535. pInfo.setLevel(parent.getLevel().getUniqueName());
  536. } else {
  537. pInfo.setRawValue("");
  538. pInfo.setFormattedValue(""); // First try to get a formatted value
  539. pInfo.setParentDimension(member.getDimension().getName());
  540. pInfo.setHierarchy(member.getHierarchy().getUniqueName());
  541. pInfo.setLevel(member.getLevel().getUniqueName());
  542. pInfo.setUniquename("");
  543. }
  544. matrix.set(x_parent, y_parent, pInfo);
  545. if (isColumns) {
  546. y_parent--;
  547. } else {
  548. x_parent--;
  549. }
  550. }
  551. }
  552. prevMembers[y] = member;
  553. prevMemberInfo[y] = memberInfo;
  554. members[y] = null;
  555. }
  556. }
  557. }
  558. }

  

saiku.table.css

  1. .workspace_results {
  2. overflow: auto;
  3. margin-right: 10px;
  4. margin-left: 10px;
  5. padding-top: 10px;
  6. line-height: 1.3;
  7. min-height: 708px;
  8. }
  9. .workspace_results table {
  10. width: auto;
  11. margin-bottom: 0;
  12. table-layout: fixed;
  13. border-spacing: 0;
  14. border-collapse: separate;
  15. border-right: 1px solid #d5d5d5;
  16. border-bottom: 1px solid #d5d5d5;
  17. }
  18. .workspace_results th,
  19. .workspace_results td {
  20. padding: 3px 9px 3px 4px;
  21. }
  22. .workspace_results td {
  23. font-size: 10px;
  24. }
  25. .workspace_results table th {
  26. margin-bottom: 0;
  27. font-weight: 500;
  28. border-left: 1px solid #d5d5d5;
  29. white-space: normal;
  30. }
  31. .workspace_results table th.all_null {
  32. border: none;
  33. text-align: center;
  34. }
  35. .workspace_results table th.col {
  36. border-top: 1px solid #d5d5d5;
  37. background: #f9f9f9;
  38. text-align: left;
  39. }
  40. .workspace_results table th.col_total_corner {
  41. border-top: 1px solid #d5d5d5;
  42. border-bottom: 1px none #d5d5d5;
  43. border-left: 1px none #d5d5d5;
  44. background: #f9f9f9;
  45. text-align: left;
  46. }
  47. .workspace_results table th.col_total_first {
  48. border-top: 1px solid #d5d5d5;
  49. border-left: 1px solid #d5d5d5;
  50. background: #f9f9f9;
  51. text-align: left;
  52. }
  53. .workspace_results table th.col_null {
  54. background: #f9f9f9;
  55. }
  56. .workspace_results table th.row {
  57. border-top: 1px solid #d5d5d5;
  58. background: #f9f9f9;
  59. text-align: left;
  60. white-space: nowrap;
  61. }
  62. .workspace_results table th.row_total {
  63. border-top: 1px solid #d5d5d5;
  64. border-left: 1px none #d5d5d5;
  65. background: #f9f9f9;
  66. text-align: left;
  67. }
  68. .workspace_results table th.row_total_first {
  69. border-top: 1px solid #d5d5d5;
  70. border-left: 1px solid #d5d5d5;
  71. background: #f9f9f9;
  72. text-align: left;
  73. }
  74. .workspace_results table th.row_total_corner {
  75. border-top: 1px none #d5d5d5;
  76. border-right: 1px none #d5d5d5;
  77. border-bottom: 1px none #d5d5d5;
  78. border-left: 1px solid #d5d5d5;
  79. background: #f9f9f9;
  80. text-align: left;
  81. }
  82. .workspace_results table th.row_header {
  83. border-top: 1px solid #d5d5d5;
  84. /*border-bottom: 1px solid #d5d5d5;*/
  85. background: #ececec;
  86. }
  87. .workspace_results table th.row_null {
  88. background: #f9f9f9;
  89. }
  90. .workspace_results table td.data {
  91. border-top: 1px solid #d5d5d5;
  92. border-left: 1px solid #d5d5d5;
  93. text-align: right;
  94. white-space: nowrap;
  95. }
  96. .workspace_results table td.total {
  97. font-weight: bold;
  98. background: #fbfbfb;
  99. }
  100. .workspace_results table tbody tr:hover {
  101. background: #edf4fa !important;
  102. }
  103. .workspace_results table td.cellhighlight:hover {
  104. background: #c2d5e2 !important;
  105. }
  106. .workspace_results table td.cellhighlight {
  107. cursor: pointer;
  108. }
  109. .workspace_results .headerhighlight th.row:hover,
  110. .workspace_results .headerhighlight th.col:hover {
  111. background: #c2d5e2 !important;
  112. }
  113. .workspace_results .headerhighlight th.row,
  114. .workspace_results .headerhighlight th.col {
  115. cursor: pointer;
  116. }
  117. .workspace_results table td.selected,
  118. .workspace_results table td.ui-selected,
  119. .workspace_results table td.ui-selecting {
  120. background: #c2d5e2 !important;
  121. }
  122. /* For some weird reason you can not have all css queries
  123. separated by commas, and have just one of these: */
  124. .workspace_results:fullscreen {
  125. width: 100%;
  126. height: 100%;
  127. background-color: #fff;
  128. font-weight: 500;
  129. }
  130. .workspace_results:-moz-full-screen {
  131. width: 100%;
  132. height: 100%;
  133. background-color: #fff;
  134. }
  135. .workspace_results:-webkit-full-screen {
  136. width: 100%;
  137. height: 100%;
  138. background-color: #fff;
  139. }
  140. .workspace_results:fullscreen table {
  141. margin: 0 auto;
  142. font-size: 200%;
  143. font-weight: 500;
  144. }
  145. .workspace_results: table td {
  146. font-size: 100%;
  147. }
  148. .workspace_results:-webkit-full-screen table {
  149. margin: 0 auto;
  150. font-size: 200%;
  151. font-weight: 500;
  152. }
  153. .workspace_results:-webkit-full-screen table td {
  154. font-size: 100%;
  155. }
  156. .workspace_results:-moz-full-screen table {
  157. margin: 0 auto;
  158. font-size: 200%;
  159. }
  160. .workspace_results:-moz-full-screen table td {
  161. font-size: 100%;
  162. }

  

转载于:https://www.cnblogs.com/DFX339/p/10966994.html

声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号