package com.wb.tool;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;

import com.wb.util.DateUtil;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.FontUnderline;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.XSSFCellStyle;
import org.apache.poi.xssf.usermodel.XSSFColor;
import org.apache.poi.xssf.usermodel.XSSFFont;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.json.JSONArray;
import org.json.JSONObject;

/**
 * 用于Excel的工具方法。
 */
public class ExcelObject {

	/**
	 * 把包含Excel数据的输入流内容转换为JSON格式的字符串。每行数据以JSONObject存储，
	 * 不同行之间以换行符分隔。首行为字段名称，字段名称允许加备注，"()"内的内容将被忽略。
	 * @param inputStream 输入流。
	 * @param xlsxFormat Excel格式，true为xlsx，false为xls。
	 * @return 转换为JSON格式的字符串。
	 * @throws Exception 转换过程发生异常。
	 */
	public static String excelToJson(InputStream inputStream, boolean xlsxFormat) throws Exception {
		Workbook book;
		Sheet sheet;
		Row row;
		Cell cell;
		Iterator<Row> rows;
		Iterator<Cell> cells;
		Object value;
		int pos, pos1, colIndex, rowIndex = 0;
		String valueStr;
		ArrayList<String> fieldList = new ArrayList<String>();
		StringBuilder text = new StringBuilder("");

		if (xlsxFormat)
			book = new XSSFWorkbook(inputStream);
		else
			book = new HSSFWorkbook(inputStream);
		try {
			sheet = book.getSheetAt(0);
			rows = sheet.rowIterator();
			while (rows.hasNext()) {
				if (rowIndex > 1)
					text.append("\n{");
				else if (rowIndex > 0)
					text.append('{');
				row = rows.next();
				cells = row.cellIterator();
				colIndex = 0;
				while (cells.hasNext()) {
					cell = cells.next();
					value = getCellValue(cell);
					if (rowIndex == 0) {
						if (value == null)
							throw new NullPointerException("Field name has null value.");
						valueStr = value.toString();
						pos = valueStr.indexOf('(');
						pos1 = valueStr.indexOf("（");
						if (pos1 != -1 && (pos == -1 || pos1 < pos))
							pos = pos1;
						if (pos == -1)
							fieldList.add(valueStr);
						else
							fieldList.add(valueStr.substring(0, pos));
					} else {
						if (colIndex > 0)
							text.append(',');
						if (colIndex >= fieldList.size())
							throw new RuntimeException(
									"Row " + (rowIndex + 1) + " column " + (colIndex + 1) + " is out of bounds.");
						text.append(com.wb.util.StringUtil.quote(fieldList.get(colIndex)));
						text.append(':');
						text.append(com.wb.util.StringUtil.encode(value));
					}
					colIndex++;
				}
				if (rowIndex > 0)
					text.append('}');
				rowIndex++;
			}
		} finally {
			book.close();
		}
		return text.toString();
	}

	/**
	 * 获取单元格用于显示的文本。
	 * @param cell 单元格。
	 * @return 单元格值。如果null返回空串。
	 */
	public static String getDisplayText(Cell cell) {
		Object object = getCellValue(cell);
		if (object == null)
			return "";
		else {
			if (object instanceof Boolean)
				return object.toString();
			else if (object instanceof Double) {
				double doubleVal = (Double) object;
				String value, format = cell.getCellStyle().getDataFormatString();

				//General为单元格常规数据格式，按数值原样进行输出
				if ("General".equals(format))
					value = com.wb.util.StringUtil.doubleToString(doubleVal);
				else
					value = com.wb.util.StringUtil.formatNumber(doubleVal, convertNumFormat(format));
				//多节格式，负数显示为红色
				if (doubleVal < 0 && format != null && format.indexOf(';') != -1 && format.indexOf("[Red]") != -1)
					value = "<span style=\"color:red;\">" + value + "</span>";
				return value;

			} else if (object instanceof Date)
				return DateUtil.format((Date) object, "yyyy/M/d");
			else
				return object.toString();
		}
	}

	/**
	 * 转换Excel数字格式为Java可用的数字格式。
	 * @param format excel数字格式。
	 * @return java数字格式。
	 */
	public static String convertNumFormat(String format) {
		if (format == null)
			return "0";
		String keys[] = { "\"", "_", "(", ")" };
		int pos = format.indexOf(';');
		if (pos != -1)
			format = format.substring(0, pos);
		for (String key : keys) {
			format = com.wb.util.StringUtil.replaceAll(format, key, "");
		}
		return format;
	}

	/**
	 * 根据对齐方法字符串返回对应Excel对齐值。
	 * @param align 对齐字符串。
	 * @return 对齐值。
	 */
	public static short getAlignment(String align, short defaultAlign) {
		if ("right".equals(align))
			return CellStyle.ALIGN_RIGHT;
		else if ("center".equals(align))
			return CellStyle.ALIGN_CENTER;
		else if ("left".equals(align))
			return CellStyle.ALIGN_LEFT;
		else
			return defaultAlign;
	}

	/**
	 * 根据Excel单元格的水平对齐值返回对应的HTML水平对齐字符串。
	 * @param align Excel单元格的对齐值。
	 * @return HTML对齐字符串。
	 */
	public static String toHtmlAlign(short align, String defaultAlign) {
		switch (align) {
		case CellStyle.ALIGN_LEFT:
			return "left";
		case CellStyle.ALIGN_CENTER:
			return "center";
		case CellStyle.ALIGN_RIGHT:
			return "right";
		case CellStyle.ALIGN_JUSTIFY:
			return "justify";
		default:
			return defaultAlign;
		}
	}

	/**
	 * 根据Excel单元格的垂直对齐值返回对应的HTML垂直对齐字符串。
	 * @param align Excel单元格的对齐值。
	 * @return HTML对齐字符串。
	 */
	public static String toHtmlVerticalAlign(short align, String defaultAlign) {
		switch (align) {
		case CellStyle.VERTICAL_TOP:
			return "top";
		case CellStyle.VERTICAL_CENTER:
			return "middle";
		case CellStyle.VERTICAL_BOTTOM:
			return "bottom";
		default:
			return defaultAlign;
		}
	}

	/**
	 * 根据设置的变量，创建并获取Excel指定版本的workbook对象。
	 * @return 新创建的workbook对象。
	 */
	public static Workbook getBook() {
		if (com.wb.common.Var.getBool("sys.service.excel.xlsx"))
			return new XSSFWorkbook();
		else
			return new HSSFWorkbook();
	}

	/**
	 * 根据设置的变量，获取Excel文件的扩展名.xls或.xlsx。
	 * @return 指定版本的Excel文件扩展名。
	 */
	public static String getExtName() {
		if (com.wb.common.Var.getBool("sys.service.excel.xlsx"))
			return ".xlsx";
		else
			return ".xls";
	}

	/**
	 * 获取前端日期时间格式对应到Excel的日期时间格式。
	 * @param format 日期时间格式。
	 * @param returnDefault 如果格式不支持是否返回默认格式，true默认格式，false返回null。
	 * @return 转换后的Excel日期时间格式。
	 */
	public static String toExcelDateFormat(String format, boolean returnDefault) {
		String[] unSupportFormats = { "N", "S", "w", "z", "W", "t", "L", "o", "u", "O", "P", "T", "Z", "c", "U", "MS",
				"time", "timestamp" };
		String[][] supportFormats = { { "d", "dd" }, { "D", "aaa" }, { "j", "d" }, { "l", "aaaa" }, { "F", "mmmm" },
				{ "m", "mm" }, { "M", "mmm" }, { "n", "m" }, { "Y", "yyyy" }, { "y", "yy" }, { "a", "am/pm" },
				{ "A", "AM/PM" }, { "g", "h" }, { "G", "hh" }, { "h", "h" }, { "H", "hh" }, { "i", "mm" },
				{ "s", "ss" } };

		for (String s : unSupportFormats) {
			if (format.indexOf(s) != -1)
				return returnDefault ? "yyyy-mm-dd" : null;
		}
		for (String[] s : supportFormats) {
			format = com.wb.util.StringUtil.replaceAll(format, s[0], s[1]);
		}
		return format;
	}

	/**
	 * 获取前端日期时间格式对应到Java的日期时间格式。
	 * @param format 日期时间格式。
	 * @param returnDefault 如果格式不支持是否返回默认格式，true默认格式，false返回null。
	 * @return 转换后的Java日期时间格式。
	 */
	public static String toJavaDateFormat(String format, boolean returnDefault) {
		String[] unSupportFormats = { "N", "S", "D", "w", "z", "W", "t", "L", "o", "O", "P", "T", "Z", "c", "U", "F",
				"MS", "l", "M", "time", "timestamp" };
		String[][] supportFormats = { { "y", "yy" }, { "Y", "yyyy" }, { "m", "MM" }, { "n", "M" }, { "d", "dd" },
				{ "j", "d" }, { "H", "HH" }, { "h", "hh" }, { "G", "H" }, { "g", "h" }, { "i", "mm" }, { "s", "ss" },
				{ "u", "SSS" }, { "a", "'_x'" }, { "A", "'_X'" } };

		for (String s : unSupportFormats) {
			if (format.indexOf(s) != -1)
				return returnDefault ? "yyyy-MM-dd" : null;
		}
		for (String[] s : supportFormats) {
			format = com.wb.util.StringUtil.replaceAll(format, s[0], s[1]);
		}
		return format;

	}

	/**
	 * 把Excel的单元格样式转换为HTML的样式。
	 * @param
	 * @return 转换后的文本。
	 */
	public static String getCellStyle(XSSFCellStyle style, boolean isNumber) {
		StringBuilder buf = new StringBuilder();
		XSSFFont font = style.getFont();
		XSSFColor color;
		String rgb;

		//设置对齐方式
		buf.append("text-align:");
		buf.append(toHtmlAlign(style.getAlignment(), isNumber ? "right" : "left"));
		buf.append(";vertical-align:");
		buf.append(toHtmlVerticalAlign(style.getVerticalAlignment(), "middle"));
		//设置字体
		buf.append(";font-family:");
		buf.append(font.getFontName());
		buf.append(";font-size:");
		buf.append(font.getFontHeightInPoints());
		buf.append("pt;font-weight:");
		buf.append(font.getBoldweight());
		rgb = getRGBColor(font.getXSSFColor());
		if (rgb != null) {
			buf.append(";color:");
			buf.append(rgb);
		}
		color = style.getFillForegroundXSSFColor();
		if (color != null) {
			rgb = getRGBColor(color);
			if (rgb != null) {
				buf.append(";background-color:");
				buf.append(rgb);
			}
		}
		if (font.getItalic())
			buf.append(";font-style:italic;");
		if (font.getStrikeout())
			buf.append(";text-decoration:line-through;");
		else if (FontUnderline.valueOf(font.getUnderline()) != FontUnderline.NONE)
			buf.append(";text-decoration:underline;");
		//设置边框线
		buf.append(";border-top:");
		buf.append(getBorderStyle(style.getBorderTop(), style.getTopBorderXSSFColor()));
		buf.append(";border-right:");
		buf.append(getBorderStyle(style.getBorderRight(), style.getRightBorderXSSFColor()));
		buf.append(";border-bottom:");
		buf.append(getBorderStyle(style.getBorderBottom(), style.getBottomBorderXSSFColor()));
		buf.append(";border-left:");
		buf.append(getBorderStyle(style.getBorderLeft(), style.getLeftBorderXSSFColor()));
		return buf.toString();
	}

	/**
	 * 获取边框css样式。
	 * @param border excel边框样式。
	 * @param color 边框颜色。
	 * @return 边框css样式。
	 */
	private static String getBorderStyle(short border, XSSFColor color) {
		String width, style, rgb;

		switch (border) {
		case CellStyle.BORDER_DOTTED:
		case CellStyle.BORDER_HAIR:
			width = "thin";
			style = "dotted";
			break;
		case CellStyle.BORDER_DASH_DOT:
		case CellStyle.BORDER_DASH_DOT_DOT:
		case CellStyle.BORDER_SLANTED_DASH_DOT:
		case CellStyle.BORDER_DASHED:
			width = "thin";
			style = "dashed";
			break;
		case CellStyle.BORDER_DOUBLE:
			width = "thin";
			style = "double";
			break;
		case CellStyle.BORDER_MEDIUM:
			width = "medium";
			style = "solid";
			break;
		case CellStyle.BORDER_MEDIUM_DASH_DOT:
		case CellStyle.BORDER_MEDIUM_DASH_DOT_DOT:
		case CellStyle.BORDER_MEDIUM_DASHED:
			width = "medium";
			style = "dashed";
			break;
		case CellStyle.BORDER_NONE:
			return "none";
		case CellStyle.BORDER_THICK:
			width = "thick";
			style = "solid";
			break;
		default:
			width = "thin";
			style = "solid";
		}
		rgb = getRGBColor(color);
		if (rgb == null)
			rgb = "black";
		return String.format("%s %s %s", width, style, rgb);
	}

	/**
	 * 把Excel颜色转换为RGB格式的颜色字符串。
	 * @param color Excel颜色。
	 * @return RGB格式的颜色字符串。如果颜色无效或未设置返回null。
	 */
	public static String getRGBColor(XSSFColor color) {
		if (color == null)
			return null;
		int red, green, blue;
		byte[] xrgb =null;
				//color.getRgbWithTint();

		if (xrgb == null)
			return null;
		red = (xrgb[0] < 0) ? (xrgb[0] + 256) : xrgb[0];
		green = (xrgb[1] < 0) ? (xrgb[1] + 256) : xrgb[1];
		blue = (xrgb[2] < 0) ? (xrgb[2] + 256) : xrgb[2];
		return String.format("#%02x%02x%02x", red, green, blue);
	}

	/**
	 * 获取单元格的值。
	 * @param cell 单元格。
	 * @return 单元格值。如果为空返回null。
	 */
	public static Object getCellValue(Cell cell) {
		switch (cell.getCellType()) {
		case Cell.CELL_TYPE_FORMULA:
		case Cell.CELL_TYPE_NUMERIC:
			if (isDateFormatted(cell))
				return cell.getDateCellValue();
			else
				return cell.getNumericCellValue();
		case Cell.CELL_TYPE_STRING:
			return cell.getStringCellValue();
		case Cell.CELL_TYPE_BOOLEAN:
			return cell.getBooleanCellValue();
		default:
			return null;
		}
	}

	/**
	 * 检查单元格是否格式化为日期。
	 * @param cell 需要判断的单元格对象。
	 * @return true格式化为日期，false没有格式化为日期。
	 */
	public static boolean isDateFormatted(Cell cell) {
		//该方法区别于DateUtil.isCellDateFormatted方法，采用格式是否包括指定字符来判断。
		String format = cell.getCellStyle().getDataFormatString();
		if (format == null)
			return true;
		if (format.toLowerCase().equals("general"))
			return false;
		if (format.startsWith("reserved"))
			return true;
		int pos = format.lastIndexOf(']');
		if (pos != -1)
			format = format.substring(pos + 1);
		return format.indexOf('0') == -1 && format.indexOf('#') == -1;
	}

	/**
	 * 判断指定单元格是否为数字或公式单元格。日期类型单元格使用数字存储，因此也属于数字单元格。
	 * @param cell 单元格。
	 * @return true数字或公式单元格，false其他类型单元格。
	 */
	public static boolean isNumericCell(Cell cell) {
		int cellType = cell.getCellType();
		return cellType == Cell.CELL_TYPE_FORMULA || cellType == Cell.CELL_TYPE_NUMERIC;
	}

	/**
	 * 填充表格数据到指定开始位置的单元格。新生成的单元格样式同起始单元格。
	 * @param sheet 填充的sheet对象。
	 * @param fill 填充的指令数据数组，每一项为一个对象。对象中name为参数名称，x为起始单元格x坐标，y为起始单元y坐标,
	 * mergeCols是合并列，mergeRows是合并行。
	 * @param params 参数对象。
	 */
	public static void fillRows(Sheet sheet, JSONArray fill, JSONObject params) {
		if (fill == null)
			return;
		int i, j = fill.length(), k, l, m, n, x, y;
		String fields[];
		JSONObject obj, mergeConfig;
		JSONArray mergeInfo, mergeRows, mergeCols, mergeItem, subItem, data;

		for (i = 0; i < j; i++) {
			obj = fill.optJSONObject(i);
			//添加数据
			x = obj.optInt("x");
			y = obj.optInt("y");
			data = com.wb.util.JsonUtil.getArray(params.opt(obj.optString("name")));
			fields = createRows(sheet, x, y, data);
			if (data == null)
				continue;
			//生成单元格合并的配置信息
			try {
				mergeConfig = new JSONObject();
				mergeRows = obj.optJSONArray("mergeRows");
				mergeCols = obj.optJSONArray("mergeCols");
				mergeInfo = new JSONArray();
				mergeConfig.put("mergeInfo", mergeInfo);
				if (mergeRows != null) {
					mergeConfig.put("mergeRows", true);
				}
				for (String field : fields) {
					mergeItem = new JSONArray();
					if (mergeRows != null)
						mergeItem.put(mergeRows.indexOf(field) != -1);
					else
						mergeItem.put(false);
					mergeItem.put(JSONObject.NULL);
					mergeInfo.put(mergeItem);
				}
				if (mergeCols != null) {
					mergeConfig.put("mergeCols", true);
					l = mergeCols.length();
					for (k = 0; k < l; k++) {
						subItem = mergeCols.getJSONArray(k);
						n = subItem.length();
						for (m = 0; m < n; m++) {
							mergeInfo.getJSONArray(com.wb.util.StringUtil.indexOf(fields, subItem.getString(m))).put(1, "g" + k);
						}
					}
				}
			} catch (Throwable e) {
				throw new IllegalArgumentException("Invalid merge config " + obj.toString());
			}
			//合并单元格
			ExcelObject.mergeCells(sheet, mergeConfig, y, y + data.length());
		}
	}

	/**
	 * 把JSONArray对象中的数据直接输出至Sheet中由x,y指定的开始单元格。该方法将在起始单元格之后插入新的行。
	 * @param sheet sheet对象。
	 * @param x 单元格x坐标。
	 * @param y 单元格y坐标。
	 * @param recs 要插入的数据。
	 * @return 填充的字段名称组成的列表。
	 */
	public static String[] createRows(Sheet sheet, int x, int y, JSONArray recs) {
		int i, j, k, l, m;
		short height;
		Row row;
		Cell cell;
		JSONObject rec;
		String fields[];
		CellStyle style[], rowStyle;
		Object objVal;

		//定义样式和数据源的首行
		row = sheet.getRow(y);
		rowStyle = row.getRowStyle();
		height = row.getHeight();
		l = row.getLastCellNum() - x;
		fields = new String[l];
		style = new CellStyle[l];
		for (k = x; k < l; k++) {
			cell = row.getCell(k);
			objVal = ExcelObject.getCellValue(cell);
			m = k - x;
			fields[m] = objVal == null ? null : com.wb.util.StringUtil.force(objVal.toString());
			style[m] = cell.getCellStyle();
		}
		//如果无数据移除空行
		if (recs == null || recs.length() == 0) {
			removeRow(sheet, y);
			return fields;
		}
		//插入recs长度的row
		j = recs.length();
		sheet.shiftRows(y, sheet.getLastRowNum(), j - 1);
		for (i = 0; i < j; i++) {
			row = sheet.createRow(i + y);
			row.setRowStyle(rowStyle);
			row.setHeight(height);
			rec = recs.optJSONObject(i);
			if (rec == null) {
				continue;
			}
			for (k = 0; k < l; k++) {
				cell = row.createCell(k + x);
				cell.setCellStyle(style[k]);
				ExcelObject.setCellValue(cell, com.wb.util.JsonUtil.opt(rec, fields[k]));
			}
		}
		return fields;
	}

	/**
	 * 设置Excel单元格的值。该方法能把值依据其不同的类型填充到单元格中。
	 * 如果值为标准字符串日期格式，在设置值前系统将自动转换为日期格式。
	 * @param cell 单元格对象。
	 * @param value 需要设置的值。
	 */
	public static void setCellValue(Cell cell, Object value) {
		String strVal;

		if (value == null)
			cell.setCellValue("");
		else if (value instanceof String) {
			strVal = (String) value;
			if (DateUtil.isDate(strVal))
				cell.setCellValue(DateUtil.strToDate(strVal));
			else {
				//多行文本
				if (strVal.indexOf('\n') != -1)
					cell.getCellStyle().setWrapText(true);
				cell.setCellValue(strVal);
			}
		} else if (value instanceof Number)
			cell.setCellValue(((Number) value).doubleValue());
		else if (value instanceof Date)
			cell.setCellValue((Date) value);
		else if (value instanceof Boolean)
			cell.setCellValue((Boolean) value);
		else {
			strVal = value.toString();
			//多行文本
			if (strVal.indexOf('\n') != -1)
				cell.getCellStyle().setWrapText(true);
			cell.setCellValue(value.toString());
		}
	}

	/**
	 * 删除Sheet指定索引的行。该方法区别于sheet.removeRow，使用该方法删除行后下方行将上移。
	 * @param sheet Sheet对象。
	 * @param rowIndex 删除的行索引号。
	 */
	public static void removeRow(Sheet sheet, int rowIndex) {
		int lastRowNum = sheet.getLastRowNum(), i, mergeRegions;
		CellRangeAddress range;

		//如果包含单元格的合并信息则予以删除
		mergeRegions = sheet.getNumMergedRegions();
		for (i = mergeRegions - 1; i >= 0; i--) {
			range = sheet.getMergedRegion(i);
			if (range.getFirstRow() <= rowIndex && range.getLastRow() >= rowIndex) {
				sheet.removeMergedRegion(i);
			}
		}
		//移动单元格
		if (rowIndex >= 0 && rowIndex < lastRowNum) {
			sheet.shiftRows(rowIndex + 1, lastRowNum, -1);
		}
		//如果为最后一行执行删除
		if (rowIndex == lastRowNum) {
			Row removingRow = sheet.getRow(rowIndex);
			if (removingRow != null) {
				sheet.removeRow(removingRow);
			}
		}
	}

	/**
	 * 执行Excel表格中的指令，指令位于最后一行并由"!!"开始。指令包括填充批量数据和合并单元格等。
	 * @param sheet Sheet对象。
	 * @param params 参数对象。
	 */
	public static void executeInstruction(Sheet sheet, JSONObject params) {
		Cell cell;
		Object value;
		String instruction;
		JSONArray ja, mergeCols;
		JSONObject jo;
		int i, j, lastRowIndex;
		Row lastRow;

		lastRowIndex = sheet.getLastRowNum();
		lastRow = sheet.getRow(lastRowIndex);
		if (lastRow == null)
			return;
		cell = lastRow.getCell(0);
		if (cell == null)
			return;
		value = ExcelObject.getCellValue(cell);
		if (value == null)
			return;
		instruction = value.toString();
		instruction = com.wb.util.WebUtil.replaceParams(params, instruction);
		if (instruction.startsWith("!!"))
			instruction = instruction.substring(2);
		else
			return;
		//删除最后一行
		ExcelObject.removeRow(sheet, lastRowIndex);
		//修改指令的语法使其符合规范
		try {
			if (instruction.startsWith("{"))
				ja = new JSONArray("[" + instruction + "]");
			else
				ja = new JSONArray(instruction);
			j = ja.length();
			for (i = 0; i < j; i++) {
				jo = ja.getJSONObject(i);
				jo.put("mergeRows", com.wb.util.JsonUtil.toArray(jo.opt("mergeRows")));
				mergeCols = jo.optJSONArray("mergeCols");
				if (mergeCols != null && mergeCols.length() > 0 && !(mergeCols.opt(0) instanceof JSONArray))
					jo.put("mergeCols", new JSONArray().put(mergeCols));
			}
		} catch (Throwable e) {
			throw new IllegalArgumentException("Invalid fill instruction " + instruction);
		}
		//填充数据
		ExcelObject.fillRows(sheet, ja, params);
	}

	/**
	 * 合并表格中需要合并的单元格。
	 * @param sheet sheet对象。
	 * @param config 合并的配置信息以象。
	 * @param startRow 开始合并的行索引号。
	 * @param endRow 结束合并的行索引号。
	 */
	public static void mergeCells(Sheet sheet, JSONObject config, int startRow, int endRow) {
		JSONArray mergeInfo = config.optJSONArray("mergeInfo");
		int i, j = mergeInfo.length(), rowIndex, colIndex, span;
		int lastRowNum = sheet.getLastRowNum(), lastColNum = j - 1;
		Iterator<Row> rows;
		Iterator<Cell> cells;
		Row row;
		Cell cell;
		Object object;
		String value, prevValue, group, prevGroup, colGroup[];
		boolean isLast;

		// 合并行
		if (config.optBoolean("mergeRows")) {
			for (i = 0; i < j; i++) {
				if (!mergeInfo.getJSONArray(i).getBoolean(0))
					continue;
				rows = sheet.rowIterator();
				span = 0;
				rowIndex = 0;
				prevValue = null;
				while (rows.hasNext()) {
					row = rows.next();
					if (rowIndex < startRow) {
						rowIndex++;
						continue;
					}
					if (rowIndex > endRow)
						break;
					cell = row.getCell(i);
					object = ExcelObject.getCellValue(cell);
					if (object == null)
						value = "";
					else
						value = object.toString();
					isLast = rowIndex == lastRowNum;
					if (prevValue != null && (!value.equals(prevValue) || isLast)) {
						if (isLast) {
							if (value.equals(prevValue))
								span++; // 如果最后一行值相等合并单元格数+1
							else
								isLast = false; // 否则不执行最后一行合并的处理
						}
						if (span > 1) {
							if (isLast)
								sheet.addMergedRegion(new CellRangeAddress(rowIndex - span + 1, rowIndex - 1, i, i));
							else
								sheet.addMergedRegion(new CellRangeAddress(rowIndex - span, rowIndex - 1, i, i));
						}
						span = 0;
					}
					prevValue = value;
					span++;
					rowIndex++;
				}
			}
		}

		// 合并列
		rowIndex = 0;
		if (config.optBoolean("mergeCols")) {
			rows = sheet.rowIterator();
			// 获取合并列的组信息到数组，以提高访问性能
			colGroup = new String[j];
			for (i = 0; i < j; i++) {
				colGroup[i] = mergeInfo.getJSONArray(i).optString(1);
			}
			while (rows.hasNext()) {
				row = rows.next();
				if (rowIndex < startRow) {
					rowIndex++;
					continue;
				}
				if (rowIndex > endRow)
					break;
				cells = row.cellIterator();
				span = 0;
				colIndex = 0;
				prevValue = null;
				prevGroup = null;
				while (cells.hasNext()) {
					cell = cells.next();
					object = ExcelObject.getCellValue(cell);
					group = colGroup[colIndex];
					if (object == null)
						value = "";
					else
						value = object.toString();
					isLast = colIndex == lastColNum;
					if (prevValue != null && (!value.equals(prevValue) || !group.equals(prevGroup) || isLast)) {
						if (isLast) {
							if (value.equals(prevValue))
								span++; // 如果最后一行值相等合并单元格数+1
							else
								isLast = false; // 否则不执行最后一行合并的处理
						}
						if (span > 1) {
							if (isLast && !group.isEmpty()) {
								sheet.addMergedRegion(
										new CellRangeAddress(rowIndex, rowIndex, colIndex - span + 1, colIndex));
							} else if (!prevGroup.isEmpty()) {
								sheet.addMergedRegion(
										new CellRangeAddress(rowIndex, rowIndex, colIndex - span, colIndex - 1));
							}
						}
						span = 0;
					}
					prevValue = value;
					prevGroup = group;
					span++;
					colIndex++;
				}
				rowIndex++;
			}
		}
	}
}