/*

{{IS_NOTE
	Purpose:
		
	Description:
		
	History:
		2013/12/01 , Created by Hawk
}}IS_NOTE

Copyright (C) 2013 Potix Corporation. All Rights Reserved.

{{IS_RIGHT
}}IS_RIGHT
*/
package io.keikai.range.impl.imexp;

import static io.keikai.range.impl.imexp.PoiEnumConversion.BOLDWEIGHT_BOLD;
import static org.apache.poi.ss.usermodel.AutoFilter.FILTEROP_VALUES;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import javax.xml.namespace.QName;

import io.keikai.model.CellRegion;
import io.keikai.model.SAutoFilter;
import io.keikai.model.SAutoFilter.FilterOp;
import io.keikai.model.SAutoFilter.NFilterColumn;
import io.keikai.model.SBook;
import io.keikai.model.SBorder.BorderType;
import io.keikai.model.SCFValueObject;
import io.keikai.model.SCellStyle;
import io.keikai.model.SChart;
import io.keikai.model.SChartAxis;
import io.keikai.model.SColor;
import io.keikai.model.SColorFilter;
import io.keikai.model.SColorScale;
import io.keikai.model.SColumnArray;
import io.keikai.model.SConditionalFormatting;
import io.keikai.model.SConditionalFormattingRule;
import io.keikai.model.SCustomFilter;
import io.keikai.model.SCustomFilters;
import io.keikai.model.SDataBar;
import io.keikai.model.SDataValidation;
import io.keikai.model.SDataValidation.ValidationType;
import io.keikai.model.SDynamicFilter;
import io.keikai.model.SExtraStyle;
import io.keikai.model.SFill.FillPattern;
import io.keikai.model.SFont;
import io.keikai.model.SIconSet;
import io.keikai.model.SPicture;
import io.keikai.model.SRichText;
import io.keikai.model.SRichText.Segment;
import io.keikai.model.SSheet;
import io.keikai.model.STable;
import io.keikai.model.STableColumn;
import io.keikai.model.STableColumn.STotalsRowFunction;
import io.keikai.model.STableStyle;
import io.keikai.model.STableStyleElem;
import io.keikai.model.STableStyleInfo;
import io.keikai.model.STop10Filter;
import io.keikai.model.SWorkbookProtection;
import io.keikai.model.ViewAnchor;
import io.keikai.model.chart.SChartData;
import io.keikai.model.chart.SGeneralChartData;
import io.keikai.model.impl.AbstractBookAdv;
import io.keikai.model.impl.AbstractChartAdv;
import io.keikai.model.impl.AbstractColorAdv;
import io.keikai.model.impl.AbstractDataValidationAdv;
import io.keikai.model.impl.AbstractFillAdv;
import io.keikai.model.impl.AbstractFontAdv;
import io.keikai.model.impl.AbstractSeriesAdv;
import io.keikai.model.impl.ObjectRefImpl;
import io.keikai.model.impl.SheetImpl;
import io.keikai.model.impl.WorkbookProtectionImpl;
import io.keikai.model.impl.chart.AreaChartDataImpl;
import io.keikai.model.impl.chart.BarChartDataImpl;
import io.keikai.model.impl.chart.BubbleChartDataImpl;
import io.keikai.model.impl.chart.CategoryAxisImpl;
import io.keikai.model.impl.chart.DateAxisImpl;
import io.keikai.model.impl.chart.GeneralChartDataImpl;
import io.keikai.model.impl.chart.LineChartDataImpl;
import io.keikai.model.impl.chart.SeriesImpl;
import io.keikai.model.impl.chart.ValueAxisImpl;
import io.keikai.model.sys.EngineFactory;
import io.keikai.model.sys.dependency.ObjectRef;
import io.keikai.model.sys.dependency.Ref;
import io.keikai.model.sys.formula.EvaluationResult;
import io.keikai.model.sys.formula.FormulaEngine;
import io.keikai.model.sys.formula.FormulaEvaluationContext;
import io.keikai.model.sys.formula.FormulaExpression;
import io.keikai.model.util.Strings;
import io.keikai.util.Converter;
import org.apache.poi.ss.SpreadsheetVersion;
import org.apache.poi.ss.usermodel.BorderStyle;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.ClientAnchor;
import org.apache.poi.ss.usermodel.Color;
import org.apache.poi.ss.usermodel.CreationHelper;
import org.apache.poi.ss.usermodel.CustomFilter;
import org.apache.poi.ss.usermodel.DataFormat;
import org.apache.poi.ss.usermodel.DataValidation;
import org.apache.poi.ss.usermodel.DataValidationConstraint;
import org.apache.poi.ss.usermodel.DxfCellStyle;
import org.apache.poi.ss.usermodel.FillPatternType;
import org.apache.poi.ss.usermodel.Font;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.RichTextString;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.VerticalAlignment;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.util.AreaReference;
import org.apache.poi.ss.util.CellAddress;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.ss.util.CellRangeAddressList;
import org.apache.poi.ss.util.CellReference;
import org.apache.poi.xddf.usermodel.XDDFColor;
import org.apache.poi.xddf.usermodel.XDDFShapeProperties;
import org.apache.poi.xddf.usermodel.XDDFSolidFillProperties;
import org.apache.poi.xddf.usermodel.chart.AxisCrosses;
import org.apache.poi.xddf.usermodel.chart.AxisPosition;
import org.apache.poi.xddf.usermodel.chart.BarDirection;
import org.apache.poi.xddf.usermodel.chart.BarGrouping;
import org.apache.poi.xddf.usermodel.chart.ChartTypes;
import org.apache.poi.xddf.usermodel.chart.DisplayBlanks;
import org.apache.poi.xddf.usermodel.chart.Grouping;
import org.apache.poi.xddf.usermodel.chart.MarkerStyle;
import org.apache.poi.xddf.usermodel.chart.XDDFArea3DChartData;
import org.apache.poi.xddf.usermodel.chart.XDDFAreaChartData;
import org.apache.poi.xddf.usermodel.chart.XDDFBar3DChartData;
import org.apache.poi.xddf.usermodel.chart.XDDFBarChartData;
import org.apache.poi.xddf.usermodel.chart.XDDFBubbleChartData;
import org.apache.poi.xddf.usermodel.chart.XDDFCategoryAxis;
import org.apache.poi.xddf.usermodel.chart.XDDFCategoryAxisHelper;
import org.apache.poi.xddf.usermodel.chart.XDDFCategoryDataSource;
import org.apache.poi.xddf.usermodel.chart.XDDFChartAxis;
import org.apache.poi.xddf.usermodel.chart.XDDFChartData;
import org.apache.poi.xddf.usermodel.chart.XDDFChartLegend;
import org.apache.poi.xddf.usermodel.chart.XDDFDataSource;
import org.apache.poi.xddf.usermodel.chart.XDDFDataSourcesFactory;
import org.apache.poi.xddf.usermodel.chart.XDDFDateAxis;
import org.apache.poi.xddf.usermodel.chart.XDDFDateAxisHelper;
import org.apache.poi.xddf.usermodel.chart.XDDFLine3DChartData;
import org.apache.poi.xddf.usermodel.chart.XDDFLineChartData;
import org.apache.poi.xddf.usermodel.chart.XDDFNumericalDataSource;
import org.apache.poi.xddf.usermodel.chart.XDDFValueAxis;
import org.apache.poi.xddf.usermodel.chart.XDDFValueAxisHelper;
import org.apache.poi.xddf.usermodel.chart.XDDFView3D;
import org.apache.poi.xssf.model.StylesTable;
import org.apache.poi.xssf.model.StylesTableHelper;
import org.apache.poi.xssf.usermodel.XSSFCellStyle;
import org.apache.poi.xssf.usermodel.XSSFChart;
import org.apache.poi.xssf.usermodel.XSSFClientAnchor;
import org.apache.poi.xssf.usermodel.XSSFColor;
import org.apache.poi.xssf.usermodel.XSSFColorFilter;
import org.apache.poi.xssf.usermodel.XSSFConditionalFormatting;
import org.apache.poi.xssf.usermodel.XSSFConditionalFormattingHelper;
import org.apache.poi.xssf.usermodel.XSSFCustomFilters;
import org.apache.poi.xssf.usermodel.XSSFDataValidationConstraint;
import org.apache.poi.xssf.usermodel.XSSFDrawing;
import org.apache.poi.xssf.usermodel.XSSFDxfCellStyle;
import org.apache.poi.xssf.usermodel.XSSFDynamicFilter;
import org.apache.poi.xssf.usermodel.XSSFFilterColumn;
import org.apache.poi.xssf.usermodel.XSSFFont;
import org.apache.poi.xssf.usermodel.XSSFRichTextString;
import org.apache.poi.xssf.usermodel.XSSFRichTextStringHelper;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFSheetHelper;
import org.apache.poi.xssf.usermodel.XSSFTable;
import org.apache.poi.xssf.usermodel.XSSFTableColumn;
import org.apache.poi.xssf.usermodel.XSSFTableColumnHelper;
import org.apache.poi.xssf.usermodel.XSSFTableStyle;
import org.apache.poi.xssf.usermodel.XSSFTableStyleInfo;
import org.apache.poi.xssf.usermodel.XSSFTop10Filter;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbookHelper;
import org.apache.poi.xssf.usermodel.extensions.XSSFCellBorder;
import org.apache.poi.xssf.usermodel.extensions.XSSFCellFill;
import org.apache.xmlbeans.XmlCursor;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTAutoFilter;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTBorder;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTCellAlignment;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTCfRule;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTCfvo;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTCol;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTColor;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTColorFilter;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTColorScale;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTConditionalFormatting;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTDataBar;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTExtension;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTExtensionList;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTFill;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTFilter;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTFilterColumn;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTFilters;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTFont;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTIconSet;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTOutlinePr;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTRst;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTSelection;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTSheetFormatPr;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTSheetPr;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTTable;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTTableStyle;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTTableStyleElement;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTTableStyles;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTWorksheet;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTXf;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.STCfType;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.STCfvoType;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.STConditionalFormattingOperator;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.STHorizontalAlignment;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.STIconSetType;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.STPatternType;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.STTableStyleType;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.STTimePeriod;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.STVerticalAlignment;

import org.zkoss.util.Pair;

/**
 * 
 * @author dennis, kuro, Hawk
 * @since 3.5.0
 */
public class ExcelXlsxExporter extends AbstractExcelExporter {
	private static final long serialVersionUID = 20141231175402L;

	protected void exportColumnArray(SSheet sheet, Sheet poiSheet, SColumnArray columnArr) {
		XSSFSheet xssfSheet = (XSSFSheet) poiSheet;
		_exportPhase.setPhase(ExportPhase.COL);

		CTWorksheet ctSheet = xssfSheet.getCTWorksheet();
		if(xssfSheet.getCTWorksheet().sizeOfColsArray() == 0) {
			xssfSheet.getCTWorksheet().addNewCols();
		}

		final int defaultWidth = sheet.getDefaultColumnWidth();
		final int columnWidth = columnArr.getWidth();
		final AbstractBookAdv book = (AbstractBookAdv)sheet.getBook();
		final int charWidth = book.getCharWidth();
		//ZSS-1402
		CTSheetFormatPr ctSheetFormatPr = ctSheet.getSheetFormatPr();
		if (ctSheetFormatPr == null) {
			ctSheetFormatPr = ctSheet.addNewSheetFormatPr();
		}
		ctSheetFormatPr.setDefaultColWidth(UnitUtil.pxToCTChar(defaultWidth, charWidth));

		//ZSS-1132
		CTCol col = ctSheet.getColsArray(0).addNewCol();
		int columnArrIndex = columnArr.getIndex();
		_exportPhase.setColIndex(columnArrIndex);
		col.setMin(columnArrIndex + 1);
		col.setMax(columnArr.getLastIndex()+1);
		col.setStyle(toPOICellStyle(columnArr.getCellStyle()).getIndex());
		col.setCustomWidth(columnArr.isCustomWidth() || columnWidth != defaultWidth); //ZSS-1132
		col.setWidth(UnitUtil.pxToCTChar(columnWidth, charWidth));
		col.setHidden(columnArr.isHidden());
		col.setBestFit(columnArr.isBestFit());

		if (columnArr.isCollapsed()) {
			col.setCollapsed(true);
		}
		if (columnArr.getOutlineLevel() > 0) {
			short outlineLevel = (short) columnArr.getOutlineLevel();
			col.setOutlineLevel(outlineLevel);
		}
	}

	@Override
	protected Workbook createPoiBook() {
		XSSFWorkbook book = new XSSFWorkbook();
		book.setCellFormulaValidation(false); // for KEIKAI-459: avoiding the fixes of ZSS-855 and ZSS-81
		return book;
	}

	/**
	 * reference DrawingManagerImpl.addChartX()
	 */
	@Override
	protected void exportChart(SSheet sheet, Sheet poiSheet) {
		_exportPhase.setPhase(ExportPhase.CHART);
		for (SChart chart: sheet.getCharts()) {
			if (!chart.isSparkline()) {
				final XSSFChart poiChart = ((XSSFDrawing)poiSheet.createDrawingPatriarch()).createChart(
						toClientAnchor(chart.getAnchor(), sheet));
				plotPoiChart(chart, poiChart);
				final List<SChartData> chartSeries = ((AbstractChartAdv) chart).getChartData();
				final boolean isCombo = chartSeries.size() > 1;
				for (final SChartData chartData: chartSeries) {
					fillPoiChartData((XSSFSheet) poiSheet, poiChart, chart, chartData, chart.isThreeD(), isCombo);
				}
			}
		}
	}

	/**
	 * Reference DrawingManagerImpl.addPicture()
	 */
	@Override
	protected void exportPicture(SSheet sheet, Sheet poiSheet) {
		_exportPhase.setPhase(ExportPhase.PICTURE);
		for (SPicture picture : sheet.getPictures()){
			int poiPictureIndex = exportedPicDataMap.get(picture.getPictureData().getIndex()); //ZSS-735
			poiSheet.createDrawingPatriarch().createPicture(toClientAnchor(picture.getAnchor(), sheet), poiPictureIndex);
		}
	}

	/**
	 *
	 * @param schartData
	 * @return a POI ChartData filled with Spreadsheet chart data, or null if the chart type is unsupported.   
	 */
	protected void fillPoiChartData(XSSFSheet poiSheet, XSSFChart poiChart, SChart chart, SChartData schartData,
			boolean isThreeD, boolean isCombo) {
		if (!(schartData instanceof SGeneralChartData)) {
			return;
		}
		XDDFChartAxis bottomAxis = null;
		XDDFValueAxis leftAxis = null;
		if (schartData instanceof GeneralChartDataImpl) {
			final GeneralChartDataImpl generalChartData = (GeneralChartDataImpl) schartData;
			final SChartAxis categoryAxis = generalChartData.getCategoryAxis();
			final SChartAxis dateAxis = generalChartData.getDateAxis();
			final List<SChartAxis> valueAxes = generalChartData.getValueAxes();
			final List<? extends XDDFChartAxis> poiAxes = poiChart.getAxes();
			final List<XDDFChartAxis> axes = new ArrayList<>();
			if (categoryAxis != null) {
				Optional<? extends XDDFChartAxis> optionalXDDFChartAxis = poiAxes.stream()
						.filter((a) -> a.getId() == categoryAxis.getId())
						.findAny();
				if (optionalXDDFChartAxis.isPresent()) {
					bottomAxis = optionalXDDFChartAxis.get();
					axes.add(bottomAxis);
				}
			}
			if (dateAxis != null) {
				Optional<? extends XDDFChartAxis> optionalXDDFChartAxis = poiAxes.stream()
						.filter((a) -> a.getId() == dateAxis.getId()).findAny();
				if (optionalXDDFChartAxis.isPresent()) {
					leftAxis = (XDDFValueAxis) optionalXDDFChartAxis.get();
					axes.add(leftAxis);
				}
			}
			if (valueAxes != null) {
				for (SChartAxis axis : valueAxes) {
					Optional<? extends XDDFChartAxis> optionalXDDFChartAxis = poiAxes.stream()
							.filter((a) -> a.getId() == axis.getId()).findAny();
					if (optionalXDDFChartAxis.isPresent()) {

						// bubble chart case, we should switch the axis here
						if (bottomAxis == null && leftAxis != null) {
							bottomAxis = leftAxis;
						}
						leftAxis = (XDDFValueAxis) optionalXDDFChartAxis.get();
						axes.add(leftAxis);
					}
				}
			}
			if (poiAxes.isEmpty() && !isCombo) {
				// TODO: create axis when insert chart
				switch (chart.getType()) {
				case AREA:
				case BAR:
				case COLUMN:
				case LINE:
					bottomAxis = poiChart.createCategoryAxis(AxisPosition.BOTTOM);
					break;
				case BUBBLE:
				case SCATTER:
					bottomAxis = poiChart.createValueAxis(AxisPosition.BOTTOM);
					break;
				}
				if (bottomAxis != null) {
					leftAxis = poiChart.createValueAxis(AxisPosition.LEFT);
					XDDFValueAxisHelper.setDelete(leftAxis, true);
					if (bottomAxis instanceof XDDFValueAxis) {
						XDDFValueAxisHelper.setDelete((XDDFValueAxis) bottomAxis, true);
					} else {
						XDDFCategoryAxisHelper.setDelete((XDDFCategoryAxis) bottomAxis, true);
					}
					bottomAxis.crossAxis(leftAxis);
					leftAxis.crossAxis(bottomAxis);
				}
			}
		}
		final SChart.ChartType chartType = ((SGeneralChartData) schartData).getType();
		XDDFChartData categoryData = null;
		switch (chartType) {
		case AREA: {
			final AreaChartDataImpl areaChartData = (AreaChartDataImpl) schartData;
			final Grouping grouping = PoiEnumConversion.toPoiGrouping(areaChartData.getGrouping());
			categoryData = poiChart.createData(
					isThreeD ? ChartTypes.AREA3D : ChartTypes.AREA, bottomAxis,
					leftAxis);
			if (isThreeD) {
				((XDDFArea3DChartData) categoryData).setGrouping(grouping);
			} else {
				((XDDFAreaChartData) categoryData).setGrouping(grouping);
			}
			break;
		}
		case COLUMN:
		case BAR: {
			final BarChartDataImpl barChartData = (BarChartDataImpl) schartData;
			final BarGrouping grouping = PoiEnumConversion.toPoiBarGrouping(barChartData.getGrouping());
			final BarDirection direction = PoiEnumConversion.toPoiBarDirection(barChartData.getBarDirection());
			categoryData = poiChart.createData(
					isThreeD ? ChartTypes.BAR3D : ChartTypes.BAR, bottomAxis,
					leftAxis);
			if (isThreeD) {
				((XDDFBar3DChartData) categoryData).setBarGrouping(grouping);
				((XDDFBar3DChartData) categoryData).setBarDirection(direction);
			} else {
				((XDDFBarChartData) categoryData).setBarGrouping(grouping);
				((XDDFBarChartData) categoryData).setBarDirection(direction);
				((XDDFBarChartData) categoryData).setOverlap((byte) barChartData.getBarOverlap()); //ZSS-830
			}
			break;
		}
		case BUBBLE: {
			final BubbleChartDataImpl bubbleChartData = (BubbleChartDataImpl) schartData;

			leftAxis.setCrosses(AxisCrosses.AUTO_ZERO);
			XDDFBubbleChartData xyzData = new XDDFBubbleChartData(poiChart, poiChart.getCTChart().getPlotArea().addNewBubbleChart(), bottomAxis, leftAxis);
			fillXYZData(poiSheet, bubbleChartData, xyzData);
			poiChart.plot(xyzData);
			break;
		}
		case DOUGHNUT: {
			categoryData = poiChart.createData(
					ChartTypes.DOUGHNUT, bottomAxis, leftAxis);
			break;
		}
		case LINE: {
			categoryData = poiChart.createData(isThreeD ? ChartTypes.LINE3D : ChartTypes.LINE, bottomAxis, leftAxis);
			break;
		}
		case PIE:
			categoryData = poiChart.createData(isThreeD ? ChartTypes.PIE3D : ChartTypes.PIE, bottomAxis, leftAxis);
			break;
		case SCATTER:
			XDDFChartData poiChartData = poiChart.createData(ChartTypes.SCATTER,
					bottomAxis, leftAxis);
			fillXYData(poiSheet, (SGeneralChartData) schartData, poiChartData);
			poiChart.plot(poiChartData);
			break;
		//			case STOCK: TODO XSSFStockChartData is implemented with errors.
		//				categoryData = new XSSFStockChartData();
		//				break;
		default:
			break;
		}
		if (categoryData != null) {
			fillCategoryData(poiSheet, (SGeneralChartData) schartData, categoryData);
			poiChart.plot(categoryData);
		}
	}

	/**
	 * Create and plot a POI chart with its chart data.
	 * @param chart
	 * @param poiChart
	 */
	protected void plotPoiChart(SChart chart, XSSFChart poiChart) {
		// KEIKAI-359
		final AbstractChartAdv chartAdv = (AbstractChartAdv) chart;
		if (chartAdv.isShowTitle()) {
			poiChart.setTitleFormula(chart.getTitle());
			final Boolean autoTitleDeleted = chartAdv.isAutoTitleDeleted();
			if (autoTitleDeleted != null) {
				poiChart.setAutoTitleDeleted(autoTitleDeleted);
			}
		}

		if (chart.isThreeD()) {
			//ZSS-830
			final XDDFView3D view3d = poiChart.getOrAddView3D();
			if (chart.getRotX() != 0) view3d.setXRotationAngle((byte) chart.getRotX());
			if (chart.getRotY() != 0) view3d.setYRotationAngle(chart.getRotY());
			if (chart.getPerspective() != 30) view3d.setPerspectiveAngle((short) chart.getPerspective());
			if (chart.getHPercent() != 100) view3d.setHPercent(chart.getHPercent());
			if (chart.getDepthPercent() != 100) view3d.setDepthPercent(chart.getDepthPercent());
			if (!chart.isRightAngleAxes()) view3d.setRightAngleAxes(false);
		}
		if (chart.getLegendPosition() != null) {
			XDDFChartLegend legend = poiChart.getOrAddLegend();
			legend.setPosition(PoiEnumConversion.toPoiLegendPosition(chart.getLegendPosition()));
		}
		for (final SChartAxis axis : chart.getCategoryAxises()) {
			final CategoryAxisImpl catAx = (CategoryAxisImpl) axis;
			final AxisPosition position = catAx.getPosition() != null ?
					PoiEnumConversion.toPoiAxisPosition(catAx.getPosition()) :  AxisPosition.BOTTOM;
			final XDDFCategoryAxis poiCatAx = poiChart
					.createCategoryAxis(position);
			XDDFCategoryAxisHelper.setId(poiCatAx,catAx.getId());
			if (catAx.getMin() != null) {
				poiCatAx.setMinimum(catAx.getMin());
			}
			if (catAx.getMax() != null) {
				poiCatAx.setMaximum(catAx.getMax());
			}
			XDDFCategoryAxisHelper.setDelete(poiCatAx, !catAx.isVisible());
			if (catAx.getOrientation() != null) {
				poiCatAx.setOrientation(PoiEnumConversion.toPoiAxisOrientation(catAx.getOrientation()));
			}
			if (catAx.getFormat() != null) {
				poiCatAx.setNumberFormat(catAx.getFormat());
			}
			if (catAx.getTickLabelPosition() != null) {
				poiCatAx.setTickLabelPosition(PoiEnumConversion.toPoiAxisTickLabelPosition(
						catAx.getTickLabelPosition()));
			}
			if (catAx.getAxisCrosses() != null) {
				poiCatAx.setCrosses(PoiEnumConversion.toPoiAxisCrosses(catAx.getAxisCrosses()));
			}
			if (catAx.getLabelAlign() != null) {
				XDDFCategoryAxisHelper.setLabelAlignment(poiCatAx, PoiEnumConversion.toPoiAxisLabelAlign(catAx.getLabelAlign()));
			}
			if (catAx.hasMajorGridline()) {
				XDDFCategoryAxisHelper.setMajorGridline(poiCatAx, catAx.hasMajorGridline());
			}
			XDDFCategoryAxisHelper.setLabelOffset(poiCatAx, catAx.getLabelOffset());
			poiChart.getAxes().stream().filter((v) -> v.getId() == catAx.getCrossAxisId()).findAny()
					.ifPresent((v) -> {
						v.crossAxis(poiCatAx);
						poiCatAx.crossAxis(v);
					});
		}

		for (final SChartAxis axis : chart.getDateAxises()) {
			final DateAxisImpl dateAx = (DateAxisImpl) axis;
			final AxisPosition position = dateAx.getPosition() != null ?
					PoiEnumConversion.toPoiAxisPosition(dateAx.getPosition()) : AxisPosition.BOTTOM;
			final XDDFDateAxis poiDateAx = poiChart
					.createDateAxis(position);
			XDDFDateAxisHelper.setId(poiDateAx, dateAx.getId());
			if (dateAx.getMin() != null) {
				poiDateAx.setMinimum(dateAx.getMin());
			}
			if (dateAx.getMax() != null) {
				poiDateAx.setMaximum(dateAx.getMax());
			}
			XDDFDateAxisHelper.setDelete(poiDateAx, !dateAx.isVisible());
			if (dateAx.getOrientation() != null) {
				poiDateAx.setOrientation(PoiEnumConversion.toPoiAxisOrientation(dateAx.getOrientation()));
			}
			if (dateAx.getTickLabelPosition() != null) {
				poiDateAx.setTickLabelPosition(PoiEnumConversion.toPoiAxisTickLabelPosition(
						dateAx.getTickLabelPosition()));
			}
			if (dateAx.getAxisCrosses() != null) {
				poiDateAx.setCrosses(PoiEnumConversion.toPoiAxisCrosses(dateAx.getAxisCrosses()));
			}
			if (dateAx.hasMajorGridline()) {
				XDDFDateAxisHelper.setMajorGridline(poiDateAx, dateAx.hasMajorGridline());
			}
			if (dateAx.getFormat() != null) {
				poiDateAx.setNumberFormat(dateAx.getFormat());
			}
			if (dateAx.getAuto() != null) {
				XDDFDateAxisHelper.setAuto(poiDateAx, dateAx.getAuto());
			}
			if (dateAx.getLblOffset() != null) {
				XDDFDateAxisHelper.setLblOffset(poiDateAx, dateAx.getLblOffset());
			}
			if (dateAx.getBaseTimeUnit() != null) {
				XDDFDateAxisHelper.setBaseTimeUnit(poiDateAx, PoiEnumConversion.toPoiTimeUnit(dateAx.getBaseTimeUnit()));
			}
			if (dateAx.getMajorUnit() != null) {
				poiDateAx.setMajorUnit(dateAx.getMajorUnit());
			}
			if (dateAx.getMajorTimeUnit() != null) {
				XDDFDateAxisHelper.setMajorTimeUnit(poiDateAx, PoiEnumConversion.toPoiTimeUnit(dateAx.getMajorTimeUnit()));
			}
			if (dateAx.getMajorUnit() != null) {
				poiDateAx.setMajorUnit(dateAx.getMajorUnit());
			}
			if (dateAx.getMinorTimeUnit() != null) {
				XDDFDateAxisHelper.setMinorTimeUnit(poiDateAx, PoiEnumConversion.toPoiTimeUnit(dateAx.getMinorTimeUnit()));
			}
			poiChart.getAxes().stream().filter((v) -> v.getId() == dateAx.getCrossAxisId()).findAny()
					.ifPresent((v) -> {
						v.crossAxis(poiDateAx);
						poiDateAx.crossAxis(v);
					});
		}

		for (final SChartAxis axis : chart.getValueAxises()) {
			final ValueAxisImpl valAx = (ValueAxisImpl) axis;
			final AxisPosition position = valAx.getPosition() != null ?
					PoiEnumConversion.toPoiAxisPosition(valAx.getPosition()) : AxisPosition.LEFT;
			final XDDFValueAxis poiValAx = poiChart
					.createValueAxis(position);
			XDDFValueAxisHelper.setId(poiValAx, valAx.getId());
			if (valAx.getMin() != null) {
				poiValAx.setMinimum(valAx.getMin());
			}
			if (valAx.getMax() != null) {
				poiValAx.setMaximum(valAx.getMax());
			}
			if (valAx.getOrientation() != null) {
				poiValAx.setOrientation(PoiEnumConversion.toPoiAxisOrientation(valAx.getOrientation()));
			}
			if (valAx.getTickLabelPosition() != null) {
				poiValAx.setTickLabelPosition(PoiEnumConversion.toPoiAxisTickLabelPosition(
						valAx.getTickLabelPosition()));
			}
			if (valAx.getAxisCrosses() != null) {
				poiValAx.setCrosses(PoiEnumConversion.toPoiAxisCrosses(valAx.getAxisCrosses()));
			}
			if (valAx.getCrossBetween() != null) {
				poiValAx.setCrossBetween(PoiEnumConversion.toPoiAxisBetween(valAx.getCrossBetween()));
			}
			XDDFValueAxisHelper.setDelete(poiValAx, !valAx.isVisible());
			if (valAx.hasMajorGridline()) {
				XDDFValueAxisHelper.setMajorGridline(poiValAx, valAx.hasMajorGridline());
			}
			if (valAx.getFormat() != null) {
				poiValAx.setNumberFormat(valAx.getFormat());
			}
			poiChart.getAxes().stream().filter((v) -> v.getId() == valAx.getCrossAxisId()).findAny()
					.ifPresent((v) -> {
						v.crossAxis(poiValAx);
						poiValAx.crossAxis(v);
					});
		}
		poiChart.setPlotOnlyVisibleCells(chart.isPlotOnlyVisibleCells());
		boolean asGap = chart.isEmptyAsGaps();
		poiChart.displayBlanksAs(asGap ? DisplayBlanks.GAP : DisplayBlanks.ZERO);
	}

	protected ClientAnchor toClientAnchor(ViewAnchor viewAnchor, SSheet sheet){
		ViewAnchor rightBottomAnchor = viewAnchor.getRightBottomAnchor(sheet);

		ClientAnchor clientAnchor = new XSSFClientAnchor(UnitUtil.pxToEmu(viewAnchor.getXOffset()),UnitUtil.pxToEmu(viewAnchor.getYOffset()),
				UnitUtil.pxToEmu(rightBottomAnchor.getXOffset()),UnitUtil.pxToEmu(rightBottomAnchor.getYOffset()),
				viewAnchor.getColumnIndex(),viewAnchor.getRowIndex(),
				rightBottomAnchor.getColumnIndex(),rightBottomAnchor.getRowIndex());

		ViewAnchor.AnchorType anchorType = viewAnchor.getAnchorType();
		if (ViewAnchor.AnchorType.MOVE_DONT_RESIZE.equals(anchorType)) {
			clientAnchor.setAnchorType(ClientAnchor.AnchorType.MOVE_DONT_RESIZE);
		} else if (ViewAnchor.AnchorType.DONT_MOVE_AND_RESIZE.equals(anchorType)) {
			clientAnchor.setAnchorType(ClientAnchor.AnchorType.DONT_MOVE_AND_RESIZE);
		} else {
			clientAnchor.setAnchorType(ClientAnchor.AnchorType.MOVE_AND_RESIZE);
		}
		return clientAnchor;
	}

	/**
	 * reference ChartDataUtil.fillCategoryData()
	 * @param chartData
	 * @param categoryData
	 */
	protected void fillCategoryData(XSSFSheet sheet, SGeneralChartData chartData, XDDFChartData categoryData) {
		FormulaExpression expression = Optional.ofNullable(((GeneralChartDataImpl) chartData).getCategoriesFormulaExpression())
				.orElse(chartData.getNumOfSeries() > 0 ?
						((SeriesImpl) chartData.getSeries(0)).getValuesFormulaExpression() :
						null);
		final XDDFCategoryDataSource categories = expression == null ? null : getCategoryDataSource(expression, sbook, sheet);

		for (int i=0 ; i < chartData.getNumOfSeries() ; i++){
			SeriesImpl series = (SeriesImpl) chartData.getSeries(i);
			XDDFNumericalDataSource<? extends Number> values = getNumericDataSource(series.getXValuesFormulaExpression(), sbook, sheet);
			// KEIKAI-11
			XDDFChartData.Series dataSerie = categoryData.addSeries(categories,
					values);
			dataSerie.setTitle(series.getName());
			if (categoryData instanceof XDDFLineChartData) {
				((XDDFLineChartData.Series) dataSerie).setSmooth(series.isSmooth());
				if (((LineChartDataImpl) chartData).hasMarker() == Boolean.TRUE) {
					((XDDFLineChartData.Series) dataSerie).setMarkerStyle(
							MarkerStyle.CIRCLE);
				}
			} else if (categoryData instanceof XDDFLine3DChartData) {
				((XDDFLine3DChartData.Series) dataSerie).setSmooth(series.isSmooth());
				if (((LineChartDataImpl) chartData).hasMarker() == Boolean.TRUE) {
					((XDDFLine3DChartData.Series) dataSerie).setMarkerStyle(
							MarkerStyle.CIRCLE);
				}
			}
			// KEIKAI-299
			if (series.getDefaultColor() != null) {
				XDDFShapeProperties shapeProperties = dataSerie.getShapeProperties();
				if (shapeProperties == null) {
					shapeProperties = new XDDFShapeProperties();
				}
				shapeProperties.setFillProperties(new XDDFSolidFillProperties(
						XDDFColor.from(series.getDefaultColor().getRGB())));
				dataSerie.setShapeProperties(shapeProperties);
			}
			if (series.getDataPointColors() != null) {
				final Map<Integer, byte[]> colors = new HashMap<>();
				for (Map.Entry<Integer,  SColor> color: series.getDataPointColors().entrySet()) {
					dataSerie.getDataPoint(color.getKey()).setFillProperties(new XDDFSolidFillProperties(XDDFColor.from(color.getValue().getRGB())));
				}
			}
		}
	}

	/**
	 * reference ChartDataUtil.fillXYData()
	 * @param chartData
	 * @param xyData
	 */
	protected void fillXYData(XSSFSheet poiSheet, SGeneralChartData chartData, XDDFChartData xyData){
		for (int i=0 ; i < chartData.getNumOfSeries() ; i++){
			final SeriesImpl series =  (SeriesImpl) chartData.getSeries(i);
			XDDFDataSource<String>  xValues = getCategoryDataSource(series.getXValuesFormulaExpression(), sbook, poiSheet);
			XDDFNumericalDataSource<? extends Number> yValues = getNumericDataSource(series.getYValuesFormulaExpression(), sbook, poiSheet);
			XDDFChartData.Series dataSerie = xyData.addSeries(xValues, yValues);
			final AbstractSeriesAdv seriesAdv = series;
			// KEIKAI-299
			if (seriesAdv.getDefaultColor() != null) {
				XDDFShapeProperties shapeProperties = dataSerie.getShapeProperties();
				if (shapeProperties == null) {
					shapeProperties = new XDDFShapeProperties();
				}
				shapeProperties.setFillProperties(new XDDFSolidFillProperties(
						XDDFColor.from(seriesAdv.getDefaultColor().getRGB())));
				dataSerie.setShapeProperties(shapeProperties);
			}
			final String titleFormula = series.getNameFormula();

			// See XlsxImporter#getTitleFormula() will return \"\" if it doesn't contain title
			if (!Strings.isBlank(titleFormula) && !titleFormula.equals("\"\"")) {
				dataSerie.setTitle(series.getName(), new CellReference(series.getNameFormula()));
			} else {
				dataSerie.setTitle(series.getName());
			}
			if (seriesAdv.getDataPointColors() != null) {
				for (Map.Entry<Integer,  SColor> color: seriesAdv.getDataPointColors().entrySet()) {
					dataSerie.getDataPoint(color.getKey()).setFillProperties(new XDDFSolidFillProperties(XDDFColor.from(color.getValue().getRGB())));
				}
			}
		}
	}

	private static XDDFCategoryDataSource getCategoryDataSource(FormulaExpression expression, SBook sBook, XSSFSheet poiSheet) {
		if (expression == null) return null;
		Ref[] areaRefs = expression.getAreaRefs();
		// not area ref
		if (areaRefs == null) {
			EvaluationResult evaluationResult = evalDataSourceFormula(
					expression, sBook, poiSheet);
			if (evaluationResult.getType() == EvaluationResult.ResultType.SUCCESS) {
				return XDDFDataSourcesFactory.fromArray(
						(String[]) ((List) evaluationResult.getValue()).toArray(new String[0]), expression.getFormulaString());
			} else {
				return XDDFDataSourcesFactory.fromArray(new String[0], expression.getFormulaString());
			}
		} else if (areaRefs.length > 1) {
			// multiple area refs
			return XDDFDataSourcesFactory.fromArray(Arrays.stream(areaRefs).map(ref -> poiSheet.getWorkbook().getSheet(ref.getSheetName()).getRow(ref.getRow()).getCell(ref.getColumn()).getStringCellValue()).collect(
							Collectors.toList()).toArray(new String[0]),
					expression.getFormulaString());
		} else {
			// normal case
			return XDDFDataSourcesFactory.fromStringCellRange(
					poiSheet.getWorkbook().getSheet(areaRefs[0].getSheetName()),
					new CellRangeAddress(areaRefs[0].getRow(),
							areaRefs[0].getLastRow(), areaRefs[0].getColumn(),
							areaRefs[0].getLastColumn()));
		}
	}

	private static XDDFNumericalDataSource<? extends Number> getNumericDataSource(FormulaExpression expression, SBook sBook, XSSFSheet poiSheet) {
		if (expression == null) return null;
		Ref[] areaRefs = expression.getAreaRefs();
		// not area ref
		if (areaRefs == null) {
			EvaluationResult evaluationResult = evalDataSourceFormula(
					expression, sBook, poiSheet);
			if (evaluationResult.getType() == EvaluationResult.ResultType.SUCCESS) {
				return XDDFDataSourcesFactory.fromArray((Number[]) evaluationResult.getValue(), expression.getFormulaString());
			} else {
				return XDDFDataSourcesFactory.fromArray(new Number[0], expression.getFormulaString());
			}
		} else if (areaRefs.length > 1) {
			// multiple area refs
			return XDDFDataSourcesFactory.fromArray(Arrays.stream(areaRefs).map(ref -> poiSheet.getWorkbook().getSheet(ref.getSheetName()).getRow(ref.getRow()).getCell(ref.getColumn()).getNumericCellValue()).collect(
					Collectors.toList()).toArray(new Double[0]),
					expression.getFormulaString());
		} else {
			// normal case
			return XDDFDataSourcesFactory.fromNumericCellRange(
					poiSheet.getWorkbook().getSheet(areaRefs[0].getSheetName()),
					new CellRangeAddress(areaRefs[0].getRow(),
							areaRefs[0].getLastRow(), areaRefs[0].getColumn(),
							areaRefs[0].getLastColumn()));
		}
	}

	private static EvaluationResult evalDataSourceFormula(FormulaExpression formula, SBook sBook, XSSFSheet poiSheet) {
		SSheet sheet = sBook.getSheet(poiSheet.getWorkbook().getSheetIndex(poiSheet.getSheetName()));
		FormulaEngine fe = EngineFactory.getInstance().createFormulaEngine();
		return fe.evaluate(formula,
				new FormulaEvaluationContext(sheet,
						new ObjectRefImpl("", poiSheet.getSheetName(), "", ObjectRef.ObjectType.CHART)));
	}
	/**
	 * reference ChartDataUtil.fillXYZData()
	 */
	protected void fillXYZData(XSSFSheet poiSheet, SGeneralChartData chartData, XDDFBubbleChartData xyzData){
		for (int i=0 ; i < chartData.getNumOfSeries() ; i++){
			final SeriesImpl series = (SeriesImpl) chartData.getSeries(i);

			XDDFDataSource<? extends Number> xValues = getNumericDataSource(series.getXValuesFormulaExpression(), sbook, poiSheet);
			XDDFNumericalDataSource<? extends Number> yValues = getNumericDataSource(series.getYValuesFormulaExpression(), sbook, poiSheet);
			XDDFNumericalDataSource<? extends Number> zValues = getNumericDataSource(series.getZValuesFormulaExpression(), sbook, poiSheet);
			final AbstractSeriesAdv seriesAdv = series;
			final XDDFBubbleChartData.Series dataSerie = (XDDFBubbleChartData.Series) xyzData.addSeries(xValues, yValues);
			dataSerie.setTitle(series.getName());
			dataSerie.setBubbleSizes(zValues);
			// KEIKAI-299
			if (seriesAdv.getDefaultColor() != null) {
				XDDFShapeProperties shapeProperties = dataSerie.getShapeProperties();
				if (shapeProperties == null) {
					shapeProperties = new XDDFShapeProperties();
				}
				shapeProperties.setFillProperties(new XDDFSolidFillProperties(
						XDDFColor.from(seriesAdv.getDefaultColor().getRGB())));
				dataSerie.setShapeProperties(shapeProperties);
			}
			if (seriesAdv.getDataPointColors() != null) {
				for (Map.Entry<Integer,  SColor> color: seriesAdv.getDataPointColors().entrySet()) {
					dataSerie.getDataPoint(color.getKey()).setFillProperties(new XDDFSolidFillProperties(XDDFColor.from(color.getValue().getRGB())));
				}
			}
		}
	}

	/**
	 * According to {@link ValidationType}, FORMULA means custom validation.
	 */
	@Override
	protected void exportValidation(SSheet sheet, Sheet poiSheet) {
		_exportPhase.setPhase(ExportPhase.DATA_VALIDATION);
		for (SDataValidation validation : sheet.getDataValidations()){
			int operatorType = PoiEnumConversion.toPoiOperatorType(validation.getOperatorType());
			String formula1 = ((AbstractDataValidationAdv)validation).getEscapedFormula1();
			String formula2 = ((AbstractDataValidationAdv)validation).getEscapedFormula2();
			DataValidationConstraint constraint = null;
			switch(validation.getValidationType()){
			case TIME:
				constraint = poiSheet.getDataValidationHelper().createTimeConstraint(operatorType, formula1, formula2);
				break;
			case TEXT_LENGTH:
				constraint = poiSheet.getDataValidationHelper().createTextLengthConstraint(operatorType, formula1, formula2);
				break;
			case DATE:
				//the last argument, dateFormat, is only used in XLS. We just pass empty string here.
				constraint = poiSheet.getDataValidationHelper().createDateConstraint(operatorType, formula1, formula2, "");
				break;
			case LIST:
				constraint = poiSheet.getDataValidationHelper().createFormulaListConstraint(formula1);
				break;
			case INTEGER:
				constraint = poiSheet.getDataValidationHelper().createIntegerConstraint(operatorType, formula1, formula2);
				break;
			case CUSTOM: // custom
				constraint = poiSheet.getDataValidationHelper().createCustomConstraint(formula1);
				break;
			case DECIMAL:
				constraint = poiSheet.getDataValidationHelper().createDecimalConstraint(operatorType, formula1, formula2);
				break;
			case ANY:
				constraint = new XSSFDataValidationConstraint(org.apache.poi.ss.usermodel.DataValidationConstraint.ValidationType.ANY, null);; //ZSS-835
				break;
			default:
				continue;
			}
			if (!validation.getRegions().isEmpty()) { // ZSS-835
				final CellRangeAddressList rgnList = new CellRangeAddressList();
				for (CellRegion rgn : validation.getRegions()) { // must prepare rgnList then create poiValidation
					rgnList.addCellRangeAddress(rgn.getRow(), rgn.getColumn(), rgn.getLastRow(), rgn.getLastColumn());
				}
				DataValidation poiValidation =
						poiSheet.getDataValidationHelper().createValidation(constraint, rgnList);

				poiValidation.setEmptyCellAllowed(validation.isIgnoreBlank());
				poiValidation.setSuppressDropDownArrow(validation.isInCellDropdown());

				poiValidation.setErrorStyle(PoiEnumConversion.toPoiErrorStyle(validation.getAlertStyle()));
				poiValidation.createErrorBox(validation.getErrorTitle(), validation.getErrorMessage());
				poiValidation.setShowErrorBox(validation.isShowError());

				poiValidation.createPromptBox(validation.getInputTitle(), validation.getInputMessage());
				poiValidation.setShowPromptBox(validation.isShowInput());

				poiSheet.addValidationData(poiValidation);
			}
		}
	}

	/**
	 * See Javadoc at {@link AbstractExcelImporter} importAutoFilter().
	 */
	@Override
	protected void exportAutoFilter(SSheet sheet, Sheet poiSheet) {
		_exportPhase.setPhase(ExportPhase.AUTO_FILTER);
		SAutoFilter autoFilter = sheet.getAutoFilter();
		if (autoFilter != null){
			CellRegion region = autoFilter.getRegion();
			poiSheet.setAutoFilter(new CellRangeAddress(region.getRow(), region.getLastRow(), region.getColumn(), region.getLastColumn()));
			int numberOfColumn = region.getLastColumn() - region.getColumn() + 1;
			exportFilterColumns(XSSFSheetHelper.getCTAutoFilter((XSSFSheet) poiSheet), autoFilter, numberOfColumn);
		}
	}

	@Override
	protected void exportSheetPr(SSheet sheet, Sheet poiSheet) {
		String tabColor = sheet.getTabColor();
		if (tabColor != null && !tabColor.isEmpty()) {
			_exportPhase.setPhase(ExportPhase.TAB_COLOR);
			((XSSFSheet) poiSheet).setTabColor(new XSSFColor(UnitUtil.hexToArgbColor(tabColor.substring(1)), null));
		}
		_exportPhase.setPhase(ExportPhase.OUTLINE);
		if (sheet.isApplyStyles()) {
			CTWorksheet ctWorksheet = ((XSSFSheet) poiSheet).getCTWorksheet();
			CTSheetPr sheetPr = ctWorksheet
					.getSheetPr();
			if (sheetPr == null) {
				sheetPr = ctWorksheet.addNewSheetPr();
			}
			CTOutlinePr ctOutlinePr = sheetPr.getOutlinePr();
			if (ctOutlinePr == null) {
				ctOutlinePr = sheetPr.addNewOutlinePr();
			}
			ctOutlinePr.setApplyStyles(true);
		}
		if (!sheet.isSummaryBelow()) {
			poiSheet.setRowSumsBelow(false);
		}
		if (!sheet.isSummaryRight()) {
			poiSheet.setRowSumsRight(false);
		}
		if (!sheet.isShowOutlineSymbols()) {
			poiSheet.setDisplayGuts(false);
		}


		_exportPhase.setPhase(ExportPhase.SHEET);
	}

	@Override
	protected void exportExtLst(SSheet sheet, Sheet poiSheet) {
		_exportPhase.setPhase(ExportPhase.EXT_LST);
		List<SChart> charts = sheet.getCharts();
		for (SChart chart : charts) {
			if (chart.isSparkline()) {
				final AbstractChartAdv chartAdv = (AbstractChartAdv) chart;
				ViewAnchor anch = chart.getAnchor();
				XSSFSheetHelper.setSparkline((XSSFSheet) poiSheet,
						((SGeneralChartData)chart.getData()).getSeries(0).getValuesFormula(),
						Converter.rangeToA1(anch.getRowIndex(), anch.getColumnIndex()), chart.getType().name(),
						chart.isWinloseSpark(), chartAdv.isSparklineFirstPointVisible(),
						chartAdv.isSparklineLastPointVisible(), chartAdv.isSparklineHighPointVisible(),
						chartAdv.isSparklineLowPointVisible(), chartAdv.isSparklineNegativePointVisible(),
						chartAdv.isSparklineMarkerVisible(), chartAdv.getSparklineSeriesColor().getHtmlColor(),
						chartAdv.getSparklineFirstPointColor().getHtmlColor(),
						chartAdv.getSparklineLastPointColor().getHtmlColor(),
						chartAdv.getSparklineHighPointColor().getHtmlColor(),
						chartAdv.getSparklineLowPointColor().getHtmlColor(),
						chartAdv.getSparklineNegativePointColor().getHtmlColor(),
						chartAdv.getSparklineMarkerColor().getHtmlColor());
			}
		}
	}

	//ZSS-1019
	protected void exportFilterColumns(CTAutoFilter poiAutoFilter, SAutoFilter autoFilter, int numberOfColumn) {
		final Map<String, Object> extra = new HashMap<String, Object>();
		int autoFilterItemIndex = 0;
		for( int i = 0 ; i < numberOfColumn ; i++){
			NFilterColumn srcFilterColumn = autoFilter.getFilterColumn(i, false);
			if (srcFilterColumn == null){
				continue;
			}
			XSSFFilterColumn destFilterColumn = null;
			if (poiAutoFilter.sizeOfFilterColumnArray() > i) {
				destFilterColumn = new XSSFFilterColumn(poiAutoFilter.getFilterColumnArray(
						i));
			} else {
				CTFilterColumn ctFilterColumn = poiAutoFilter.insertNewFilterColumn(
						autoFilterItemIndex);
				autoFilterItemIndex++;
				destFilterColumn = new XSSFFilterColumn(ctFilterColumn);
				ctFilterColumn.setColId(i);
			}
			Object[] criteria1 = null;
			if (srcFilterColumn.getCriteria1()!=null){
				criteria1 = srcFilterColumn.getCriteria1().toArray(new String[0]);
			}
			Object[] criteria2 = null;
			if (srcFilterColumn.getCriteria1()!=null){
				criteria2 = srcFilterColumn.getCriteria2().toArray(new String[0]);
			}

			//ZSS-1191
			final SColorFilter colorFilter = srcFilterColumn.getColorFilter();
			XSSFColorFilter poiFilter = null;
			if (colorFilter != null) {
				final SExtraStyle extraStyle = colorFilter.getExtraStyle();
				addPOIDxfCellStyle(extraStyle);
				final XSSFDxfCellStyle poiCellStyle =
						(XSSFDxfCellStyle) styleTable.get(extraStyle);
				poiFilter = new XSSFColorFilter(poiCellStyle, colorFilter.isByFontColor());
			}
			extra.put("colorFilter", poiFilter);

			//ZSS-1224
			final SCustomFilters customFilters = srcFilterColumn.getCustomFilters();
			if (customFilters != null) {
				XSSFCustomFilters poiCustomFilters = new XSSFCustomFilters(destFilterColumn);
				poiCustomFilters.setAnd(customFilters.isAnd());
				final SCustomFilter srcFilter1 = customFilters.getCustomFilter1();
				final SCustomFilter srcFilter2 = customFilters.getCustomFilter2();
				poiCustomFilters.addCustomFilter(toPOIOpertor(srcFilter1.getOperator()), srcFilter1.getValue());
				if (srcFilter2 != null) {
					poiCustomFilters.addCustomFilter(toPOIOpertor(srcFilter2.getOperator()), srcFilter2.getValue());
				}
				extra.put("customFilters", poiCustomFilters); //ZSS-1224
			}

			//ZSS-1226
			final SDynamicFilter dynamicFilter = srcFilterColumn.getDynamicFilter();
			if (dynamicFilter != null) {
				XSSFDynamicFilter poiDynamicFilter = new XSSFDynamicFilter(destFilterColumn);
				poiDynamicFilter.setProperties(dynamicFilter.getMaxValue(), dynamicFilter.getValue(), dynamicFilter.getType()); //ZSS-1234
				extra.put("dynamicFilter", poiDynamicFilter);
			}

			//ZSS-1227
			final STop10Filter top10Filter = srcFilterColumn.getTop10Filter();
			if (top10Filter != null) {
				XSSFTop10Filter poiTop10Filter = new XSSFTop10Filter(destFilterColumn);
				poiTop10Filter.setProperties(top10Filter.isTop(), top10Filter.getValue(), top10Filter.isPercent(), top10Filter.getFilterValue());
				extra.put("top10Filter", poiTop10Filter);
			}

			//ZSS-1191
			destFilterColumn.setProperties(criteria1, PoiEnumConversion.toPoiFilterOperator(srcFilterColumn.getOperator()),
					criteria2, srcFilterColumn.isShowButton(), extra);

		}
	}
	private Set getCriteriaSet(Object criteria) {
		final Set set = new HashSet();
		if (criteria instanceof String[]) {
			String[] strings = (String[]) criteria;
			for(int j = 0; j < strings.length; ++j) {
				set.add(strings[j]);
			}
		}
		return set;
	}
	private void _setProperties(CTFilterColumn ctfc, Object criteria1, int filterOp, Object criteria2, Boolean visibleDropDown, Map<String, Object> extra) {
		int _operator = filterOp;
		Set _criteria1 = getCriteriaSet(criteria1);
		Set _criteria2 = getCriteriaSet(criteria2);
		boolean blank1 = _criteria1.contains("=");

		//ZSS-1191
		Pair<DxfCellStyle, Boolean> _colorFilter = (Pair<DxfCellStyle, Boolean>) extra.get("colorFilter");
		if (_colorFilter != null) {
			if (ctfc.getColorFilter() == null) {
				final CTColorFilter ctFilter = ctfc.addNewColorFilter();
				ctFilter.setDxfId(_colorFilter.getX().getIndex());
				if (_colorFilter.getY()) {
					ctFilter.setCellColor(false);
				} else if (ctFilter.isSetCellColor()) {
					ctFilter.unsetCellColor();
				}
			}
		}

		if (visibleDropDown != null) {
			if (visibleDropDown.booleanValue()) {
				if (ctfc.isSetHiddenButton()) { //ZSS-1019
					ctfc.unsetHiddenButton();
				}
			} else {
				ctfc.setHiddenButton(true); //ZSS-1019
			}
		}

		if (_criteria1.isEmpty()) { //remove filtering
			if (ctfc.isSetFilters()) {
				ctfc.unsetFilters();
			}
			return;
		}

		//TODO, more filtering operation
		switch(_operator) {
		case FILTEROP_VALUES:
			final String[] filters = (String[]) criteria1;
			//remove old
			if (ctfc.isSetFilters()) {
				ctfc.unsetFilters();
			}
			final CTFilters cflts = ctfc.addNewFilters();
			if (blank1) {
				cflts.setBlank(blank1);
			}
			for(int j = 0; j < filters.length; ++j) {
				final CTFilter cflt = cflts.addNewFilter();
				cflt.setVal(filters[j]);
			}
		}
	}

	//ZSS-1224
	private CustomFilter.Operator toPOIOpertor(FilterOp op) {
		return CustomFilter.Operator.valueOf(op.name());
	}

	/**
	 * Export hashed password directly to poiSheet.
	 */
	@Override
	protected void exportPassword(SSheet sheet, Sheet poiSheet) {

		//ZSS-1063
		final String hashValue = ((SheetImpl)sheet).getHashValue();
		if (hashValue != null) {
			final String saltValue = ((SheetImpl)sheet).getSaltValue();
			final String spinCount = ((SheetImpl)sheet).getSpinCount();
			final String algName = ((SheetImpl)sheet).getAlgName();

			XSSFSheetHelper.setSheetPassword((XSSFSheet)poiSheet, saltValue, hashValue, spinCount, algName);
		} else {
			short hashpass = sheet.getHashedPassword();
			if (hashpass != 0) {
				XSSFSheetHelper.setSheetPasswordHash((XSSFSheet)poiSheet, hashpass);
			}
		}
	}

	private XSSFWorkbook getWorkbook() {
		return (XSSFWorkbook) workbook;
	}

	//ZSS-854 
	@Override
	protected CellStyle toPOIDefaultCellStyle(SCellStyle cellStyle) {
		//set Border
		BorderStyle bottom = PoiEnumConversion.toPoiBorderType(cellStyle.getBorderBottom());
		BorderStyle left = PoiEnumConversion.toPoiBorderType(cellStyle.getBorderLeft());
		BorderStyle right = PoiEnumConversion.toPoiBorderType(cellStyle.getBorderRight());
		BorderStyle top = PoiEnumConversion.toPoiBorderType(cellStyle.getBorderTop());
		Color bottomColor = toPOIColor(cellStyle.getBorderBottomColor());
		Color leftColor = toPOIColor(cellStyle.getBorderLeftColor());
		Color rightColor = toPOIColor(cellStyle.getBorderRightColor());
		Color topColor = toPOIColor(cellStyle.getBorderTopColor());
		CTBorder ct = CTBorder.Factory.newInstance();
		XSSFCellBorder border = new XSSFCellBorder(ct);
		// prepareBorder
		border.setBorderStyle(XSSFCellBorder.BorderSide.LEFT, left);
		border.setBorderStyle(XSSFCellBorder.BorderSide.TOP, top);
		border.setBorderStyle(XSSFCellBorder.BorderSide.RIGHT, right);
		border.setBorderStyle(XSSFCellBorder.BorderSide.BOTTOM, bottom);
		if (left != BorderStyle.NONE) {
			border.setBorderColor(XSSFCellBorder.BorderSide.LEFT, (XSSFColor)leftColor);
		}
		if (top != BorderStyle.NONE) {
			border.setBorderColor(XSSFCellBorder.BorderSide.TOP, (XSSFColor)topColor);
		}
		if (right != BorderStyle.NONE) {
			border.setBorderColor(XSSFCellBorder.BorderSide.RIGHT, (XSSFColor)rightColor);
		}
		if (bottom != BorderStyle.NONE) {
			border.setBorderColor(XSSFCellBorder.BorderSide.BOTTOM, (XSSFColor)bottomColor);
		}
		// fill
		//ZSS-857: SOLID pattern; switch fgColor and bgColor 
		SColor fgColor = cellStyle.getFillColor();
		SColor bgColor = cellStyle.getBackColor();
		if (cellStyle.getFillPattern() == FillPattern.SOLID) {
			SColor tmp = fgColor;
			fgColor = bgColor;
			bgColor = tmp;
		}
		Color fillColor = toPOIColor(fgColor);
		Color backColor = toPOIColor(bgColor);
		FillPatternType pattern = PoiEnumConversion.toPoiFillPattern(cellStyle.getFillPattern());
		CTFill ctf = CTFill.Factory.newInstance();
		XSSFCellFill fill = new XSSFCellFill(ctf, null);
		prepareFill(fill, fillColor, backColor, pattern);

		// font
		XSSFFont font = (XSSFFont)toPOIFont(cellStyle.getFont());

		// refer from BookHelper#setDataFormat
		DataFormat df = getWorkbook().createDataFormat();
		short fmt = df.getFormat(cellStyle.getDataFormat());

		XSSFCellStyle poiCellStyle = XSSFWorkbookHelper.createDefaultCellStyle(getWorkbook(), border, fill, font, fmt);

		//cell Alignment
		HorizontalAlignment hAlign = PoiEnumConversion.toPoiHorizontalAlignment(cellStyle.getAlignment());
		VerticalAlignment vAlign = PoiEnumConversion.toPoiVerticalAlignment(cellStyle.getVerticalAlignment());
		boolean wrapText = cellStyle.isWrapText();
		setDefaultCellAlignment(poiCellStyle.getStyleXf(), hAlign, vAlign, wrapText);

		//protect
		boolean locked = cellStyle.isLocked();
		boolean hidden = cellStyle.isHidden();
		poiCellStyle.setLocked(locked);
		poiCellStyle.setHidden(hidden);

		return poiCellStyle;
	}
	private static void setDefaultCellAlignment(CTXf _cellStyleXf, HorizontalAlignment hAlign, VerticalAlignment vAlign, boolean wrapText) {
		HorizontalAlignment defaultHAlign = HorizontalAlignment.GENERAL;
		VerticalAlignment defaultVAlign = VerticalAlignment.BOTTOM;
		boolean defaultWrapText = false;

		if (_cellStyleXf != null) {
			if (_cellStyleXf.isSetAlignment()) {
				CTCellAlignment ctalign = _cellStyleXf.getAlignment();
				if (ctalign.isSetHorizontal()) {
					defaultHAlign = HorizontalAlignment.forInt(ctalign.getHorizontal().intValue() - 1);
				}
				if (ctalign.isSetVertical()) {
					defaultVAlign = VerticalAlignment.forInt(ctalign.getVertical().intValue() - 1);
				}
				if (ctalign.isSetWrapText()) {
					defaultWrapText = ctalign.getWrapText();
				}
			}
		}

		if (defaultHAlign != hAlign && hAlign != HorizontalAlignment.GENERAL) {
			if (!_cellStyleXf.isSetAlignment()) {
				_cellStyleXf.setAlignment(CTCellAlignment.Factory.newInstance());
			}
			_cellStyleXf.getAlignment().setHorizontal(
					STHorizontalAlignment.Enum.forInt(hAlign.getCode() + 1));
		}
		if (defaultVAlign != vAlign && vAlign != VerticalAlignment.BOTTOM) {
			if (!_cellStyleXf.isSetAlignment()) {
				_cellStyleXf.setAlignment(CTCellAlignment.Factory.newInstance());
			}
			_cellStyleXf.getAlignment().setVertical(
					STVerticalAlignment.Enum.forInt(vAlign.getCode() + 1));
		}
		if (defaultWrapText != wrapText && wrapText) {
			if (!_cellStyleXf.isSetAlignment()) {
				_cellStyleXf.setAlignment(CTCellAlignment.Factory.newInstance());
			}
			_cellStyleXf.getAlignment().setWrapText(wrapText);
		}
	}

	private void prepareFill(XSSFCellFill fill, Color fillColor, Color backColor, FillPatternType patternType) {
		XSSFColor fc = (XSSFColor) fillColor;
		XSSFColor bc = (XSSFColor) backColor;
		//ZSS-797
		String fHex = fc != null ? fc.getARGBHex() : null;
		String bHex = bc != null ? bc.getARGBHex() : null;
		boolean fcset = fHex != null && !"FF000000".equalsIgnoreCase(fHex);
		boolean bcset = bHex != null && !"FFFFFFFF".equalsIgnoreCase(bHex);
		if (bcset || fcset) {
			fill.setFillForegroundColor(fc == null ?
					new XSSFColor(new byte[] {(byte)0xff, (byte)0xff, (byte)0xff}) : fc);
		}
		if (bcset) {
			fill.setFillBackgroundColor(bc == null ?
					new XSSFColor(new byte[] {(byte)0xff, (byte)0xff, (byte)0xff}) : bc);
		}
		fill.setPatternType(STPatternType.Enum.forInt(patternType.getCode() + 1));
	}

	//ZSS-855
	@Override
	protected int exportTables(SSheet sheet, Sheet poiSheet0, int tbId) {
		final XSSFSheet poiSheet = (XSSFSheet) poiSheet0;
		for (STable table : sheet.getTables()) {
			_exportPhase.setPhase(ExportPhase.TABLE);
			XSSFTable poiTable = poiSheet.createTable(new AreaReference(
					table.getAllRegion().getRegion().getReferenceString(),
					SpreadsheetVersion.EXCEL2007));
			poiTable.setName(table.getName());
			String displayName = table.getDisplayName();
			_exportPhase.setTableName(displayName);
			poiTable.setDisplayName(displayName);
			CTTable ctTable = poiTable.getCTTable();
			if (table.getTotalsRowCount() == 0) {
				if (ctTable.isSetTotalsRowCount()) {
					ctTable.unsetTotalsRowCount();
				}
			} else {
				ctTable.setTotalsRowCount(table.getTotalsRowCount());
			}
			if (table.getHeaderRowCount() == 0) {
				if (ctTable.isSetHeaderRowCount()) {
					ctTable.unsetHeaderRowCount();
				}
			} else {
				ctTable.setHeaderRowCount(table.getHeaderRowCount());
			}
			_exportPhase.setPhase(ExportPhase.TABLE_STYLE);
			final XSSFTableStyleInfo poiInfo = new XSSFTableStyleInfo(
					getWorkbook().getStylesSource(),
					ctTable.addNewTableStyleInfo());
			final STableStyleInfo info = table.getTableStyleInfo();
			poiInfo.setName(info.getName());
			poiInfo.setShowColumnStripes(info.isShowColumnStripes());
			poiInfo.setShowRowStripes(info.isShowRowStripes());
			poiInfo.setLastColumn(info.isShowLastColumn());
			poiInfo.setFirstColumn(info.isShowFirstColumn());
			_exportPhase.setPhase(ExportPhase.TABLE);

			final SAutoFilter filter = table.getAutoFilter();
			if (filter != null) {
				_exportPhase.setPhase(ExportPhase.TABLE_AUTO_FILTER);
				final CellRegion region = filter.getRegion();
				CTAutoFilter ctAutoFilter = null;
				if (!(ctTable.isSetHeaderRowCount() && ctTable.getHeaderRowCount() == 0) && !ctTable.isSetAutoFilter()) {
					ctAutoFilter = ctTable.addNewAutoFilter();
					if (ctTable.isSetAutoFilter()) {
						ctAutoFilter.setRef(region.getReferenceString());
					}
				}
				if (ctAutoFilter != null) {
					exportFilterColumns(ctAutoFilter, filter, region.getColumnCount());
				}
				_exportPhase.setPhase(ExportPhase.TABLE);
			} else {
				if (ctTable.isSetAutoFilter()) {
					ctTable.unsetAutoFilter();
				}
			}

			int j = 0;
			List<XSSFTableColumn> poiTableColumns = poiTable.getColumns();
			for (STableColumn tbCol : table.getColumns()) {
				_exportPhase.setPhase(ExportPhase.TABLE_COLUMN);

				// KEIKAI-550: the column name cannot be enclosed by a space.
				String tbColName = tbCol.getName().trim();
				final XSSFTableColumn poiTbCol = poiTableColumns.get(j);

				_exportPhase.setTableColumnName(tbColName);
				poiTbCol.setName(tbColName.replaceAll("\n", "_x000a_")); //KEIKAI-512
				poiTbCol.setId(++j);
				if (tbCol.getTotalsRowFunction() != null) {
					XSSFTableColumnHelper.setTotalsRowFunction(poiTbCol, XSSFTableColumnHelper.TotalsRowFunction.values()[tbCol.getTotalsRowFunction()
							.ordinal()]);
				}
				if (tbCol.getTotalsRowFunction() == STotalsRowFunction.none && tbCol.getTotalsRowLabel() != null)
					XSSFTableColumnHelper.setTotalsRowLabel(poiTbCol, tbCol.getTotalsRowLabel());
				else if (tbCol.getTotalsRowFunction() == STotalsRowFunction.custom && tbCol.getTotalsRowFormula() != null)
					XSSFTableColumnHelper.setTotalsRowFormula(poiTbCol, tbCol.getTotalsRowFormula()); //ZSS-977
				_exportPhase.setTableColumnName(null);
			}
			_exportPhase.setPhase(ExportPhase.TABLE);
			ctTable.setId(++tbId);
//			getWorkbook().addTableName(poiTable);
			_exportPhase.setTableName(null);
			_exportPhase.setPhase(null);
		}

		return tbId;
	}

	//ZSS-1145
	protected void addPOIDxfCellStyle(SExtraStyle extraStyle) {
		// instead of creating a new style, use old one if exist
		XSSFDxfCellStyle poiCellStyle = (XSSFDxfCellStyle) styleTable.get(extraStyle);
		if (poiCellStyle != null) {
			//			workbook.addDxfCellStyle(poiCellStyle); //ZSS-1191: already in workbook; no need to add again
			return;
		}
		poiCellStyle = XSSFWorkbookHelper.createDxfCellStyle(getWorkbook()); //will add into workbook

		// Border
		if (extraStyle.getBorder() != null) {
			final BorderType btb = extraStyle.getBorderBottom();
			final BorderType btl = extraStyle.getBorderLeft();
			final BorderType btr = extraStyle.getBorderRight();
			final BorderType btt = extraStyle.getBorderTop();
			final BorderType btd = extraStyle.getBorderDiagonal();
			final BorderType bth = extraStyle.getBorderHorizontal();
			final BorderType btv = extraStyle.getBorderVertical();

			final BorderStyle bottom = btb == null ? BorderStyle.NONE : PoiEnumConversion.toPoiBorderType(btb);
			final BorderStyle left = btl == null ? BorderStyle.NONE : PoiEnumConversion.toPoiBorderType(btl);
			final BorderStyle right = btr == null ? BorderStyle.NONE : PoiEnumConversion.toPoiBorderType(btr);
			final BorderStyle top =  btt == null ? BorderStyle.NONE : PoiEnumConversion.toPoiBorderType(btt);
			final BorderStyle diagonal = btd == null ? BorderStyle.NONE : PoiEnumConversion.toPoiBorderType(btd);
			final BorderStyle horizontal = bth == null ? BorderStyle.NONE : PoiEnumConversion.toPoiBorderType(bth);
			final BorderStyle vertical = btv == null ? BorderStyle.NONE : PoiEnumConversion.toPoiBorderType(btv);

			final Color bottomColor = toPOIColor(extraStyle.getBorderBottomColor());
			final Color leftColor = toPOIColor(extraStyle.getBorderLeftColor());
			final Color rightColor = toPOIColor(extraStyle.getBorderRightColor());
			final Color topColor = toPOIColor(extraStyle.getBorderTopColor());
			final Color diagonalColor = toPOIColor(extraStyle.getBorderDiagonalColor());
			final Color horizontalColor = toPOIColor(extraStyle.getBorderHorizontalColor());
			final Color verticalColor = toPOIColor(extraStyle.getBorderVerticalColor());

			boolean diaUp = extraStyle.isShowDiagonalUpBorder();
			boolean diaDn = extraStyle.isShowDiagonalDownBorder();

			poiCellStyle.setBorder(left, leftColor, top, topColor, right, rightColor, bottom, bottomColor, diagonal, diagonalColor, horizontal, horizontalColor, vertical, verticalColor, diaUp, diaDn);
		}

		// Fill
		final AbstractFillAdv fill = (AbstractFillAdv) extraStyle.getFill();
		if (fill != null) {
			SColor fgColor = fill.getRawFillColor();
			SColor bgColor = fill.getRawBackColor();
			FillPattern fillPattern = fill.getRawFillPattern();

			// ZSS-992: in <dxf>, bgColor and fgColor is set as is; (whilst <xf> is reversed)
			//			if (fillPattern == null || fillPattern == FillPattern.SOLID) { //ZSS-1162
			//				SColor tmp = fgColor;
			//				fgColor = bgColor;
			//				bgColor = tmp;
			//			}
			Color fillColor = fgColor == null ? null : toPOIColor(fgColor);
			Color backColor = bgColor == null ? null : toPOIColor(bgColor);
			short pattern = fillPattern == null ? -1 : PoiEnumConversion.toPoiFillPattern(extraStyle.getFillPattern()).getCode();
			poiCellStyle.setFill(fillColor, backColor, pattern);
		}

		//		//cell Alignment
		//		short hAlign = PoiEnumConversion.toPoiHorizontalAlignment(extraStyle.getAlignment());
		//		short vAlign = PoiEnumConversion.toPoiVerticalAlignment(extraStyle.getVerticalAlignment());
		//		boolean wrapText = extraStyle.isWrapText();
		//
		//		//ZSS-1020
		//		poiCellStyle.setCellAlignment(hAlign, vAlign, wrapText, (short) extraStyle.getRotation());
		//
		//		//protect
		//		boolean locked = extraStyle.isLocked();
		//		boolean hidden = extraStyle.isHidden();
		//		poiCellStyle.setProtection(locked, hidden);

		// NumFmt
		// TODO: if custom formatCode?
		final String dataFormat = extraStyle.getDataFormat();
		if (dataFormat != null) {
			DataFormat df = workbook.createDataFormat();
			short fmt = df.getFormat(dataFormat);
			poiCellStyle.setDataFormat(fmt);
		}

		// Font
		poiCellStyle.setFont(toPOIDxfFont(extraStyle.getFont()));

		// put into table
		styleTable.put(extraStyle, poiCellStyle);

		//		int indention = extraStyle.getIndention();
		//		if (indention > 0)
		//			poiCellStyle.setIndention((short) indention);
	}

	//ZSS-1145
	protected Font toPOIDxfFont(SFont font0) {
		if (font0 == null) return null; //ZSS-1138

		XSSFFont poiFont = new XSSFFont(CTFont.Factory.newInstance());;
		AbstractFontAdv font = (AbstractFontAdv) font0;

		if (font.isOverrideBold())
			poiFont.setBold(PoiEnumConversion.toPoiBoldweight(font.getBoldweight()) == BOLDWEIGHT_BOLD);
		if (font.isOverrideStrikeout())
			poiFont.setStrikeout(font.isStrikeout());
		if (font.isOverrideItalic())
			poiFont.setItalic(font.isItalic());
		if (font.isOverrideColor())
			BookHelper.setFontColor(workbook, poiFont, toPOIColor(font.getColor()));
		if (font.isOverrideHeightPoints())
			poiFont.setFontHeightInPoints((short) font.getHeightPoints());
		if (font.isOverrideName())
			poiFont.setFontName(font.getName());
		if (font.isOverrideTypeOffset())
			poiFont.setTypeOffset(PoiEnumConversion.toPoiTypeOffset(font.getTypeOffset()));
		if (font.isOverrideUnderline())
			poiFont.setUnderline(PoiEnumConversion.toPoiUnderline(font.getUnderline()));

		return poiFont;
	}

	//ZSS-1141
	@Override
	protected void exportConditionalFormatting(SSheet sheet, Sheet poiSheet) {
		_exportPhase.setPhase(ExportPhase.CONDITIONAL_FORMATTING);
		final List<SConditionalFormatting> formattings = sheet.getConditionalFormattings();
		XSSFSheet xssfSheet = (XSSFSheet) poiSheet;
		Map<CTCfRule, String> extRuleIdMap = new HashMap<>();
		for (SConditionalFormatting cf : formattings) {
			boolean isUnderExt = cf.isUnderExt();
			final XSSFConditionalFormatting poicf = XSSFConditionalFormattingHelper.newXSSFConditionalFormatting(xssfSheet);
			final CTConditionalFormatting ctcf = XSSFConditionalFormattingHelper.getCTConditionalFormatting(poicf);
			addSqref(ctcf, cf);

			boolean isExtExpression = false; // only support ext expression, todo: databar, iconset, ...

			for (SConditionalFormattingRule rule : cf.getRules()) {
				final CTCfRule ctRule = ctcf.addNewCfRule();
				if (!isExtExpression)
					isExtExpression = isUnderExt && rule.getType().equals(SConditionalFormattingRule.RuleType.EXPRESSION);
				if (!isUnderExt || isExtExpression) {
					addPoiRule(sheet, ctRule, rule);
					if (isExtExpression)
						extRuleIdMap.put(ctRule, rule.getId());
				}
			}

			if (isExtExpression) {
				XSSFSheetHelper.addConditionalFormattingToExtensionList(xssfSheet, poicf, extRuleIdMap);
			}

			if (!isUnderExt) {
				xssfSheet.getSheetConditionalFormatting().addConditionalFormatting(poicf);
			}
		}
	}

	//ZSS-1141
	protected void addSqref(CTConditionalFormatting ctcf, SConditionalFormatting cf) {
		StringBuilder sb = new StringBuilder();
		for (CellRegion rgn : cf.getRegions()) {
			if (sb.length() > 0) {
				sb.append(" ");
			}
			sb.append(rgn.getReferenceString());
		}
		List<String> sqrefs = new ArrayList<String>();
		sqrefs.add(sb.toString());
		ctcf.setSqref(sqrefs);
	}

	//ZSS-1141
	protected void addPoiRule(SSheet sheet, CTCfRule ctRule, SConditionalFormattingRule rule) {
		ctRule.setType(toConditionalFormattingRuleType(rule.getType()));
		Integer priority = rule.getPriority();
		if (priority != null)
			ctRule.setPriority(priority);
		if (rule.isStopIfTrue()) {
			ctRule.setStopIfTrue(true);
		}
		final SExtraStyle extraStyle = rule.getExtraStyle();
		if (extraStyle != null) {
			addPOIDxfCellStyle(extraStyle);
			final XSSFDxfCellStyle poiCellStyle = (XSSFDxfCellStyle) styleTable.get(extraStyle);
			int index = poiCellStyle.getIndex();
			if (index >= 0)
				ctRule.setDxfId(index);
		}
		switch(rule.getType()) {
		case ABOVE_AVERAGE:
			if (!rule.isAboveAverage())
				ctRule.setAboveAverage(false);
			if (rule.isEqualAverage())
				ctRule.setEqualAverage(true);
			if (rule.getStandardDeviation() != null)
				ctRule.setStdDev(rule.getStandardDeviation());
			break;
		case CELL_IS:
			if (rule.getOperator() != null)
				ctRule.setOperator(toCFRuleOperator(rule.getOperator()));
			addFormulas(ctRule, rule);
			break;
		case COLOR_SCALE:
			if (rule.getColorScale() != null)
				addColorScale(ctRule, rule);
			break;
		case CONTAINS_BLANKS:
		case NOT_CONTAINS_BLANKS:
			addFormulas(ctRule, rule);
			break;
		case CONTAINS_ERRORS:
		case NOT_CONTAINS_ERRORS:
			addFormulas(ctRule, rule);
			break;
		case CONTAINS_TEXT:
		case NOT_CONTAINS_TEXT:
			if (rule.getText() != null)
				ctRule.setText(rule.getText());
			if (rule.getOperator() != null)
				ctRule.setOperator(toCFRuleOperator(rule.getOperator()));
			addFormulas(ctRule, rule);
			break;
		case DATA_BAR:
			if (rule.getDataBar() != null)
				addDataBar(ctRule, rule);
			break;
		case BEGINS_WITH:
		case ENDS_WITH:
			if (rule.getText() != null)
				ctRule.setText(rule.getText());
			if (rule.getOperator() != null)
				ctRule.setOperator(toCFRuleOperator(rule.getOperator()));
			addFormulas(ctRule, rule);
			break;
		case EXPRESSION:
			addFormulas(ctRule, rule);
			break;
		case ICON_SET:
			if (rule.getIconSet() != null)
				addIconSet(ctRule, rule);
			break;
		case TIME_PERIOD:
			if (rule.getTimePeriod() != null)
				ctRule.setTimePeriod(toTimePeriod(rule.getTimePeriod()));
			addFormulas(ctRule, rule);
			break;
		case TOP_10:
			if (rule.getRank() != null)
				ctRule.setRank(rule.getRank());
			if (rule.isPercent())
				ctRule.setPercent(true);
			if (rule.isBottom())
				ctRule.setBottom(true);
			break;
		case DUPLICATE_VALUES:
		case UNIQUE_VALUES:
			// No extra setting
			break;
		}

		// KEIKAI-469
		if (rule.getExtId() != null) {
			CTExtensionList ctExtensionList = ctRule.addNewExtLst();
			CTExtension ctExtension = ctExtensionList.addNewExt();
			XmlCursor cursor = ctExtension.newCursor();
			cursor.toNextToken();
			final String x14NS = "http://schemas.microsoft.com/office/spreadsheetml/2009/9/main";
			cursor.insertNamespace("x14", x14NS);
			cursor.insertAttributeWithValue("uri", "{B025F937-C7B1-47D3-B67F-A62EFF666E3E}");
			cursor.insertElementWithText(new QName(x14NS, "id"), rule.getExtId());
		}
	}

	//ZSS-1141
	protected void addIconSet(CTCfRule ctRule, SConditionalFormattingRule rule) {
		final CTIconSet ctIconSet = ctRule.addNewIconSet();
		final SIconSet iconSet = rule.getIconSet();
		for (SCFValueObject vo : iconSet.getCFValueObjects()) {
			final CTCfvo ctvo = ctIconSet.addNewCfvo();
			addValueObject(ctvo, vo);
		}

		ctIconSet.setIconSet(toIconSetType(iconSet.getType()));
		if (iconSet.isPercent())
			ctIconSet.setPercent(true);
		if (iconSet.isReverse())
			ctIconSet.setReverse(true);
		if (!iconSet.isShowValue())
			ctIconSet.setShowValue(false);
	}

	//ZSS-1141
	protected void addColorScale(CTCfRule ctRule, SConditionalFormattingRule rule) {
		final SColorScale colorScale = rule.getColorScale();
		final CTColorScale ctColorScale = ctRule.addNewColorScale();
		for (SCFValueObject vo : colorScale.getCFValueObjects()) {
			final CTCfvo ctvo = ctColorScale.addNewCfvo();
			addValueObject(ctvo, vo);
		}

		for (SColor color : colorScale.getColors()) {
			CTColor ctColor = ctColorScale.addNewColor();
			ctColor.setRgb(((AbstractColorAdv)color).getARGB());
		}
	}

	//ZSS-1141
	protected void addDataBar(CTCfRule ctRule, SConditionalFormattingRule rule) {
		final CTDataBar ctDataBar = ctRule.addNewDataBar();
		final SDataBar dataBar = rule.getDataBar();
		for (SCFValueObject vo : dataBar.getCFValueObjects()) {
			final CTCfvo ctvo = ctDataBar.addNewCfvo();
			addValueObject(ctvo, vo);
		}
		CTColor ctColor = ctDataBar.addNewColor();
		ctColor.setRgb(((AbstractColorAdv)dataBar.getColor()).getARGB());

		if (dataBar.getMaxLength() != 90) {
			ctDataBar.setMaxLength(dataBar.getMaxLength());
		}
		if (dataBar.getMinLength() != 10) {
			ctDataBar.setMinLength(dataBar.getMinLength());
		}
		if (!dataBar.isShowValue()) {
			ctDataBar.setShowValue(false);
		}
	}

	//ZSS-1141
	protected void addFormulas(CTCfRule ctRule, SConditionalFormattingRule rule) {
		//ZSS-1142
		if (rule.getFormula1() != null) {
			ctRule.addFormula(rule.getFormula1().substring(1)); // don't include the leading = character
		}
		if (rule.getFormula2() != null) {
			ctRule.addFormula(rule.getFormula2().substring(1)); // don't include the leading = character
		}
		if (rule.getFormula3() != null) {
			ctRule.addFormula(rule.getFormula3().substring(1)); // don't include the leading = character
		}
	}

	//ZSS-1141
	protected void addValueObject(CTCfvo ctvo, SCFValueObject vo) {
		if (vo.isGreaterOrEqual()) {
			//ZSS-1142
			if (ctvo.isSetGte()) {
				ctvo.unsetGte();
			}
		}
		ctvo.setType(toValueObjectType(vo.getType()));
		if (vo.getValue() != null) {
			ctvo.setVal(vo.getValue());
		}
	}

	//ZSS-1141
	protected STTimePeriod.Enum toTimePeriod(SConditionalFormattingRule.RuleTimePeriod ctPeriod) {
		return STTimePeriod.Enum.forInt(ctPeriod.value);
	}
	//ZSS-1141
	protected STConditionalFormattingOperator.Enum toCFRuleOperator(SConditionalFormattingRule.RuleOperator ctType) {
		return STConditionalFormattingOperator.Enum.forInt(ctType.value);
	}

	//ZSS-1141
	protected STIconSetType.Enum toIconSetType(SIconSet.IconSetType  ctType) {
		return STIconSetType.Enum.forInt(ctType.value);
	}

	//ZSS-1141
	protected STCfvoType.Enum toValueObjectType(SCFValueObject.CFValueObjectType ctType) {
		return STCfvoType.Enum .forInt(ctType.value);
	}

	//ZSS-1141
	protected STCfType.Enum toConditionalFormattingRuleType(SConditionalFormattingRule.RuleType cfType) {
		return STCfType.Enum.forInt(cfType.value);
	}

	//ZSS-1141
	protected STCfType.Enum toConditionalFormatingRuleType(SConditionalFormattingRule.RuleType stype) {
		return STCfType.Enum.forInt(stype.value);
	}

	//ZSS-1188
	protected void addPOITableStyle(STableStyle tbStyle) {
		// instead of creating a new style, use old one if exist
		XSSFTableStyle poiTbStyle = (XSSFTableStyle) tbStyleTable.get(tbStyle);
		StylesTable stylesSource = getWorkbook().getStylesSource();
		if (poiTbStyle != null) {
			StylesTableHelper.addTableStyle(stylesSource, poiTbStyle);
			return;
		}
		poiTbStyle = (XSSFTableStyle) StylesTableHelper.createTableStyle(getWorkbook().getStylesSource(), tbStyle.getName()); //will add into workbook

		final STableStyleElem wholeTable = tbStyle.getWholeTableStyle();
		CTTableStyles tableStyles = stylesSource.getCTStylesheet()
				.getTableStyles();
		CTTableStyle ctTableStyle = tableStyles.getTableStyleArray(
				poiTbStyle.getIndex());
		if (wholeTable != null) {
			final int dxfId = getOrCreateDxfId(wholeTable);
			CTTableStyleElement ctTableStyleElement = ctTableStyle.addNewTableStyleElement();
			ctTableStyleElement.setDxfId(dxfId);
			ctTableStyleElement.setType(STTableStyleType.WHOLE_TABLE);
		}

		final STableStyleElem colStripe1 = tbStyle.getColStripe1Style();
		if (colStripe1 != null) {
			final int colStripe1Size = tbStyle.getColStripe1Size();
			final int dxfId = getOrCreateDxfId(colStripe1);
			CTTableStyleElement ctTableStyleElement = ctTableStyle.addNewTableStyleElement();
			ctTableStyleElement.setDxfId(dxfId);
			ctTableStyleElement.setType(STTableStyleType.FIRST_COLUMN_STRIPE);
			if (colStripe1Size > 1) {
				ctTableStyleElement.setSize(colStripe1Size);;
			}
		}

		final STableStyleElem colStripe2 = tbStyle.getColStripe2Style();
		if (colStripe2 != null) {
			final int colStripe2Size = tbStyle.getColStripe2Size();
			final int dxfId = getOrCreateDxfId(colStripe2);
			CTTableStyleElement ctTableStyleElement = ctTableStyle.addNewTableStyleElement();
			ctTableStyleElement.setDxfId(dxfId);
			ctTableStyleElement.setType(STTableStyleType.SECOND_COLUMN_STRIPE);
			if (colStripe2Size > 1) {
				ctTableStyleElement.setSize(colStripe2Size);;
			}
		}

		final STableStyleElem rowStripe1 = tbStyle.getRowStripe1Style();
		if (rowStripe1 != null) {
			final int rowStripe1Size = tbStyle.getRowStripe1Size();
			final int dxfId = getOrCreateDxfId(rowStripe1);
			CTTableStyleElement ctTableStyleElement = ctTableStyle.addNewTableStyleElement();
			ctTableStyleElement.setDxfId(dxfId);
			ctTableStyleElement.setType(STTableStyleType.FIRST_ROW_STRIPE);
			if (rowStripe1Size > 1) {
				ctTableStyleElement.setSize(rowStripe1Size);;
			}
		}

		final STableStyleElem rowStripe2 = tbStyle.getRowStripe2Style();
		if (rowStripe2 != null) {
			final int rowStripe2Size = tbStyle.getRowStripe2Size();
			final int dxfId = getOrCreateDxfId(rowStripe2);

			CTTableStyleElement ctTableStyleElement = ctTableStyle.addNewTableStyleElement();
			ctTableStyleElement.setDxfId(dxfId);
			ctTableStyleElement.setType(STTableStyleType.SECOND_ROW_STRIPE);
			if (rowStripe2Size > 1) {
				ctTableStyleElement.setSize(rowStripe2Size);;
			}
		}

		final STableStyleElem lastColumn = tbStyle.getLastColumnStyle();
		if (lastColumn != null) {
			final int dxfId = getOrCreateDxfId(lastColumn);
			CTTableStyleElement ctTableStyleElement = ctTableStyle.addNewTableStyleElement();
			ctTableStyleElement.setDxfId(dxfId);
			ctTableStyleElement.setType(STTableStyleType.LAST_COLUMN);
		}

		final STableStyleElem firstColumn = tbStyle.getFirstColumnStyle();
		if (firstColumn != null) {
			final int dxfId = getOrCreateDxfId(firstColumn);
			CTTableStyleElement ctTableStyleElement = ctTableStyle.addNewTableStyleElement();
			ctTableStyleElement.setDxfId(dxfId);
			ctTableStyleElement.setType(STTableStyleType.FIRST_COLUMN);
		}

		final STableStyleElem headerRow = tbStyle.getHeaderRowStyle();
		if (headerRow != null) {
			final int dxfId = getOrCreateDxfId(headerRow);
			CTTableStyleElement ctTableStyleElement = ctTableStyle.addNewTableStyleElement();
			ctTableStyleElement.setDxfId(dxfId);
			ctTableStyleElement.setType(STTableStyleType.HEADER_ROW);
		}

		final STableStyleElem totalRow = tbStyle.getTotalRowStyle();
		if (totalRow != null) {
			final int dxfId = getOrCreateDxfId(totalRow);
			CTTableStyleElement ctTableStyleElement = ctTableStyle.addNewTableStyleElement();
			ctTableStyleElement.setDxfId(dxfId);
			ctTableStyleElement.setType(STTableStyleType.TOTAL_ROW);
		}

		final STableStyleElem firstHeaderCell = tbStyle.getFirstHeaderCellStyle();
		if (firstHeaderCell != null) {
			final int dxfId = getOrCreateDxfId(firstHeaderCell);

			CTTableStyleElement ctTableStyleElement = ctTableStyle.addNewTableStyleElement();
			ctTableStyleElement.setDxfId(dxfId);
			ctTableStyleElement.setType(STTableStyleType.FIRST_HEADER_CELL);
		}

		final STableStyleElem lastHeaderCell = tbStyle.getLastHeaderCellStyle();
		if (lastHeaderCell != null) {
			final int dxfId = getOrCreateDxfId(lastHeaderCell);
			CTTableStyleElement ctTableStyleElement = ctTableStyle.addNewTableStyleElement();
			ctTableStyleElement.setDxfId(dxfId);
			ctTableStyleElement.setType(STTableStyleType.LAST_HEADER_CELL);
		}

		final STableStyleElem firstTotalCell = tbStyle.getFirstTotalCellStyle();
		if (firstTotalCell != null) {
			final int dxfId = getOrCreateDxfId(firstTotalCell);
			CTTableStyleElement ctTableStyleElement = ctTableStyle.addNewTableStyleElement();
			ctTableStyleElement.setDxfId(dxfId);
			ctTableStyleElement.setType(STTableStyleType.FIRST_TOTAL_CELL);
		}

		final STableStyleElem lastTotalCell = tbStyle.getLastTotalCellStyle();
		if (lastTotalCell != null) {
			final int dxfId = getOrCreateDxfId(lastTotalCell);
			CTTableStyleElement ctTableStyleElement = ctTableStyle.addNewTableStyleElement();
			ctTableStyleElement.setDxfId(dxfId);
			ctTableStyleElement.setType(STTableStyleType.LAST_TOTAL_CELL);
		}

		// put into table
		tbStyleTable.put(tbStyle, poiTbStyle);
	}

	//ZSS-1188
	protected int getOrCreateDxfId(STableStyleElem tbStyleElem) {
		int index = getOrCreateDxfId0(tbStyleElem);
		if (index < 0) {
			addPOIDxfCellStyle(tbStyleElem);
			return getOrCreateDxfId0(tbStyleElem);
		}
		return index;
	}
	//ZSS-1188
	private int getOrCreateDxfId0(STableStyleElem tbStyleElem) {
		XSSFDxfCellStyle poiCellStyle = (XSSFDxfCellStyle) styleTable.get(tbStyleElem);
		if (poiCellStyle != null) {
			return poiCellStyle.getDxfIndex();
		}
		return -1;
	}
	//ZSS-1189
	@Override
	protected RichTextString toPOIRichText(SRichText richText) {
		//ZSS-1189
		CreationHelper helper = workbook.getCreationHelper();
		XSSFRichTextString poiRichTextString =
				(XSSFRichTextString) helper.createRichTextString(richText.getText());

		for (Segment sg : richText.getSegments()) {
			SFont font = sg.getFont();
			String text = sg.getText();
			CTRst st = poiRichTextString.getCTRst();
			if(st.sizeOfRArray() == 0 && st.isSetT()) {
				st.unsetT();
			}
			XSSFRichTextStringHelper.addRun(poiRichTextString, text, (XSSFFont)toPOIFont(font));
		}

		return poiRichTextString;
	}
	// KEIKAI-489
	protected void exportWorkbookProtection(SWorkbookProtection workbookProtection) {
		final WorkbookProtectionImpl protectionImpl =  (WorkbookProtectionImpl) workbookProtection;
		final XSSFWorkbook xBook = getWorkbook();
		if (protectionImpl.isLockStructure() != null) {
			if (protectionImpl.isLockStructure()) {
				xBook.lockStructure();
			} else {
				xBook.unLockStructure();
			}
		}
		if (protectionImpl.isLockWindows() != null) {
			if (protectionImpl.isLockWindows()) {
				xBook.lockWindows();
			} else {
				xBook.unLockWindows();
			}
		}
		if (protectionImpl.getAlgName() != null) {
			XSSFWorkbookHelper.setAlgName(xBook, protectionImpl.getAlgName());
		}
		if (protectionImpl.getHashValue() != null) {
			XSSFWorkbookHelper.setHashValue(xBook, protectionImpl.getHashValue());
		}
		if (protectionImpl.getSaltValue() != null) {
			XSSFWorkbookHelper.setSaltValue(xBook, protectionImpl.getSaltValue());
		}
		if (protectionImpl.getSpinCount() != null) {
			XSSFWorkbookHelper.setSpinCount(xBook, protectionImpl.getSpinCount());
		}
	}
	protected void exportRowColumn(SSheet sheet, Sheet poiSheet) {
		super.exportRowColumn(sheet, poiSheet);
		poiSheet.setActiveCell(new CellAddress(sheet.getViewInfo().getActiveCell()));
		CTSelection selectionArray = ((XSSFSheet) poiSheet).getCTWorksheet()
				.getSheetViews().getSheetViewArray(0).getSelectionArray(0);
		selectionArray.setSqref(Arrays.asList(sheet.getViewInfo().getSelectionAreas().split(" ")));
	}
}
