package jnpf.tmsUtil; import jnpf.exception.DataException; import jnpf.util.StringUtil; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.CellType; import org.apache.poi.ss.usermodel.DataFormatter; 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.usermodel.WorkbookFactory; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import javax.xml.parsers.DocumentBuilderFactory; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.io.InputStreamReader; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; /** * TMS Excel 导入:支持 SpreadsheetML(.xls 本系统导出)、POI 二进制/xlsx、UTF-8 CSV。 */ public final class TmsExcelImportHelper { private static final DataFormatter FORMATTER = new DataFormatter(); private TmsExcelImportHelper() { } /** * 读取首个工作表全部行(含表头),单元格统一转字符串。 */ public static List> readRows(byte[] bytes, String fileName) { if (bytes == null || bytes.length == 0) { throw new DataException("上传文件为空"); } String name = fileName == null ? "" : fileName.toLowerCase(); try { if (name.endsWith(".csv") || looksLikeCsv(bytes)) { return readCsv(bytes); } if (looksLikeSpreadsheetMl(bytes)) { return readSpreadsheetMl(bytes); } return readWithPoi(bytes); } catch (DataException e) { throw e; } catch (Exception e) { throw new DataException("无法解析导入文件,请使用模板填写后上传(xls/xlsx/csv)"); } } private static boolean looksLikeSpreadsheetMl(byte[] bytes) { String head = new String(bytes, 0, Math.min(bytes.length, 200), StandardCharsets.UTF_8).trim(); return head.startsWith("= 2 && bytes[0] == 'P' && bytes[1] == 'K') { return false; } if (looksLikeSpreadsheetMl(bytes)) { return false; } String head = new String(bytes, 0, Math.min(bytes.length, 120), StandardCharsets.UTF_8); return head.contains(",") && !head.contains("> readWithPoi(byte[] bytes) throws Exception { try (InputStream in = new ByteArrayInputStream(bytes); Workbook workbook = WorkbookFactory.create(in)) { Sheet sheet = workbook.getSheetAt(0); List> rows = new ArrayList<>(); int last = sheet.getLastRowNum(); for (int i = 0; i <= last; i++) { Row row = sheet.getRow(i); if (row == null) { continue; } int lastCell = Math.max(row.getLastCellNum(), 0); List cells = new ArrayList<>(lastCell); boolean any = false; for (int c = 0; c < lastCell; c++) { String v = cellText(row.getCell(c)); if (StringUtil.isNotEmpty(v)) { any = true; } cells.add(v); } if (any) { rows.add(cells); } } return rows; } } private static String cellText(Cell cell) { if (cell == null) { return ""; } if (cell.getCellType() == CellType.FORMULA) { try { return FORMATTER.formatCellValue(cell).trim(); } catch (Exception ignored) { return ""; } } return FORMATTER.formatCellValue(cell).trim(); } private static List> readSpreadsheetMl(byte[] bytes) throws Exception { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setNamespaceAware(true); factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); Document doc = factory.newDocumentBuilder().parse(new ByteArrayInputStream(bytes)); NodeList rowNodes = doc.getElementsByTagNameNS("urn:schemas-microsoft-com:office:spreadsheet", "Row"); if (rowNodes.getLength() == 0) { rowNodes = doc.getElementsByTagName("Row"); } List> rows = new ArrayList<>(rowNodes.getLength()); for (int i = 0; i < rowNodes.getLength(); i++) { Element rowEl = (Element) rowNodes.item(i); NodeList children = rowEl.getChildNodes(); List cells = new ArrayList<>(); boolean any = false; int col = 0; for (int j = 0; j < children.getLength(); j++) { Node n = children.item(j); if (!(n instanceof Element)) { continue; } Element cell = (Element) n; if (!"Cell".equals(cell.getLocalName()) && !"Cell".equals(cell.getTagName())) { continue; } String idx = cell.getAttributeNS("urn:schemas-microsoft-com:office:spreadsheet", "Index"); if (StringUtil.isEmpty(idx)) { idx = cell.getAttribute("ss:Index"); } if (StringUtil.isNotEmpty(idx)) { int target = Integer.parseInt(idx) - 1; while (col < target) { cells.add(""); col++; } } String text = dataText(cell); if (StringUtil.isNotEmpty(text)) { any = true; } cells.add(text); col++; } if (any) { rows.add(cells); } } return rows; } private static String dataText(Element cell) { NodeList dataNodes = cell.getElementsByTagNameNS("urn:schemas-microsoft-com:office:spreadsheet", "Data"); if (dataNodes.getLength() == 0) { dataNodes = cell.getElementsByTagName("Data"); } if (dataNodes.getLength() == 0) { return ""; } return dataNodes.item(0).getTextContent() == null ? "" : dataNodes.item(0).getTextContent().trim(); } private static List> readCsv(byte[] bytes) throws Exception { Charset charset = detectCharset(bytes); try (BufferedReader reader = new BufferedReader( new InputStreamReader(new ByteArrayInputStream(bytes), charset))) { List> rows = new ArrayList<>(); String line; while ((line = reader.readLine()) != null) { if (StringUtil.isEmpty(line.trim())) { continue; } if (rows.isEmpty() && line.startsWith("\uFEFF")) { line = line.substring(1); } rows.add(parseCsvLine(line)); } return rows; } } private static Charset detectCharset(byte[] bytes) { if (bytes.length >= 3 && (bytes[0] & 0xFF) == 0xEF && (bytes[1] & 0xFF) == 0xBB && (bytes[2] & 0xFF) == 0xBF) { return StandardCharsets.UTF_8; } // Excel 中文环境常存 GBK String asUtf8 = new String(bytes, 0, Math.min(bytes.length, 200), StandardCharsets.UTF_8); if (asUtf8.contains("题库") || asUtf8.contains("题型") || asUtf8.contains("题干")) { return StandardCharsets.UTF_8; } return Charset.forName("GBK"); } private static List parseCsvLine(String line) { List cells = new ArrayList<>(); StringBuilder cur = new StringBuilder(); boolean inQuote = false; for (int i = 0; i < line.length(); i++) { char ch = line.charAt(i); if (inQuote) { if (ch == '"') { if (i + 1 < line.length() && line.charAt(i + 1) == '"') { cur.append('"'); i++; } else { inQuote = false; } } else { cur.append(ch); } } else if (ch == '"') { inQuote = true; } else if (ch == ',') { cells.add(cur.toString().trim()); cur.setLength(0); } else { cur.append(ch); } } cells.add(cur.toString().trim()); return cells; } }