|
|
@@ -61,131 +61,133 @@ public class HtmlTableToExcelConverter {
|
|
|
Map<Integer, Set<Integer>> occupiedCells = new HashMap<>();
|
|
|
|
|
|
int excelRowNum = 0;
|
|
|
- for (Element tr : table.select("tr")) {
|
|
|
- // 跳过空行
|
|
|
- if (tr.children().isEmpty()) {
|
|
|
- excelRowNum++;
|
|
|
- continue;
|
|
|
- }
|
|
|
+ if (table != null) {
|
|
|
+ for (Element tr : table.select("tr")) {
|
|
|
+ // 跳过空行
|
|
|
+ if (tr.children().isEmpty()) {
|
|
|
+ excelRowNum++;
|
|
|
+ continue;
|
|
|
+ }
|
|
|
|
|
|
- // 创建行并设置默认行高(比默认值小)
|
|
|
- Row excelRow = sheet.createRow(excelRowNum);
|
|
|
+ // 创建行并设置默认行高(比默认值小)
|
|
|
+ Row excelRow = sheet.createRow(excelRowNum);
|
|
|
|
|
|
|
|
|
- // 初始化当前行占用集合
|
|
|
- occupiedCells.putIfAbsent(excelRowNum, new HashSet<>());
|
|
|
+ // 初始化当前行占用集合
|
|
|
+ occupiedCells.putIfAbsent(excelRowNum, new HashSet<>());
|
|
|
|
|
|
- int excelColNum = 0;
|
|
|
+ int excelColNum = 0;
|
|
|
|
|
|
|
|
|
- //是否设置行高
|
|
|
- Boolean isHeight = false;
|
|
|
+ //是否设置行高
|
|
|
+ Boolean isHeight = false;
|
|
|
|
|
|
- for (Element td : tr.select("td")) {
|
|
|
+ for (Element td : tr.select("td")) {
|
|
|
|
|
|
- //设置行高
|
|
|
- String style1 = td.attr("style");
|
|
|
- if (!isHeight && StringUtil.isNotBlank(style1)) {
|
|
|
- List<String> collect = Arrays.stream(style1.split(";")).collect(Collectors.toList());
|
|
|
- HashMap<String, String> map = new HashMap<>();
|
|
|
- collect.forEach(s -> {
|
|
|
- map.put(s.split(":")[0], s.split(":")[1]);
|
|
|
- });
|
|
|
+ //设置行高
|
|
|
+ String style1 = td.attr("style");
|
|
|
+ if (!isHeight && StringUtil.isNotBlank(style1)) {
|
|
|
+ List<String> collect = Arrays.stream(style1.split(";")).collect(Collectors.toList());
|
|
|
+ HashMap<String, String> map = new HashMap<>();
|
|
|
+ collect.forEach(s -> {
|
|
|
+ map.put(s.split(":")[0], s.split(":")[1]);
|
|
|
+ });
|
|
|
|
|
|
- String height = map.get("height");
|
|
|
- Float rowHeight = 16f; // 默认16点
|
|
|
- if (StringUtil.isNotBlank(height)) {
|
|
|
- isHeight = true;
|
|
|
- height = height.replace("px", "");
|
|
|
- try {
|
|
|
- Float px = Float.valueOf(height);
|
|
|
- rowHeight = px * 0.75f; // 像素转点 (1px ≈ 0.75pt)
|
|
|
- } catch (Exception ignored) {
|
|
|
- ignored.printStackTrace();
|
|
|
- }
|
|
|
+ String height = map.get("height");
|
|
|
+ Float rowHeight = 16f; // 默认16点
|
|
|
+ if (StringUtil.isNotBlank(height)) {
|
|
|
+ isHeight = true;
|
|
|
+ height = height.replace("px", "");
|
|
|
+ try {
|
|
|
+ Float px = Float.valueOf(height);
|
|
|
+ rowHeight = px * 0.75f; // 像素转点 (1px ≈ 0.75pt)
|
|
|
+ } catch (Exception ignored) {
|
|
|
+ ignored.printStackTrace();
|
|
|
+ }
|
|
|
|
|
|
- excelRow.setHeightInPoints(rowHeight);
|
|
|
+ excelRow.setHeightInPoints(rowHeight);
|
|
|
+ }
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
|
|
|
- // 跳过已被占用的单元格
|
|
|
- while (isCellOccupied(occupiedCells, excelRowNum, excelColNum)) {
|
|
|
- excelColNum++;
|
|
|
- }
|
|
|
+ // 跳过已被占用的单元格
|
|
|
+ while (isCellOccupied(occupiedCells, excelRowNum, excelColNum)) {
|
|
|
+ excelColNum++;
|
|
|
+ }
|
|
|
|
|
|
- // 处理列跨度和行跨度
|
|
|
- int colspan = getSpan(td, "colspan");
|
|
|
- int rowspan = getSpan(td, "rowspan");
|
|
|
+ // 处理列跨度和行跨度
|
|
|
+ int colspan = getSpan(td, "colspan");
|
|
|
+ int rowspan = getSpan(td, "rowspan");
|
|
|
|
|
|
- // 获取单元格内容
|
|
|
- String cellText = extractCellText(td);
|
|
|
+ // 获取单元格内容
|
|
|
+ String cellText = extractCellText(td);
|
|
|
|
|
|
- // 创建单元格
|
|
|
- Cell cell = excelRow.createCell(excelColNum);
|
|
|
- cell.setCellValue(cellText);
|
|
|
+ // 创建单元格
|
|
|
+ Cell cell = excelRow.createCell(excelColNum);
|
|
|
+ cell.setCellValue(cellText);
|
|
|
|
|
|
- // 应用样式
|
|
|
- String styleKey = getCellStyleKey(td, cssRules);
|
|
|
- if (!styleCache.containsKey(styleKey)) {
|
|
|
- CellStyle style = createCellStyle(workbook, td, cssRules, fontCache);
|
|
|
- styleCache.put(styleKey, style);
|
|
|
+ // 应用样式
|
|
|
+ String styleKey = getCellStyleKey(td, cssRules);
|
|
|
+ if (!styleCache.containsKey(styleKey)) {
|
|
|
+ CellStyle style = createCellStyle(workbook, td, cssRules, fontCache);
|
|
|
+ styleCache.put(styleKey, style);
|
|
|
|
|
|
- // 检查是否需要自动换行
|
|
|
- if (shouldWrapText(td, cssRules, cellText)) {
|
|
|
- style.setWrapText(true);
|
|
|
+ // 检查是否需要自动换行
|
|
|
+ if (shouldWrapText(td, cssRules, cellText)) {
|
|
|
+ style.setWrapText(true);
|
|
|
+ }
|
|
|
}
|
|
|
- }
|
|
|
- cell.setCellStyle(styleCache.get(styleKey));
|
|
|
-
|
|
|
- // 记录列宽(优先使用HTML中的宽度)
|
|
|
- if (td.hasAttr("width")) {
|
|
|
- String widthStr = td.attr("width").replace("px", "");
|
|
|
- try {
|
|
|
- float px = Float.parseFloat(widthStr);
|
|
|
- float charWidth = px / 7f; // 像素转字符宽度 (1字符≈7px)
|
|
|
-
|
|
|
- // 考虑跨列情况:总宽度分配到各列
|
|
|
- for (int i = 0; i < colspan; i++) {
|
|
|
- int colIdx = excelColNum + i;
|
|
|
- float perColWidth = charWidth / colspan;
|
|
|
-
|
|
|
- // 取最大宽度作为列宽
|
|
|
- columnWidths.putIfAbsent(colIdx, 0f);
|
|
|
- if (perColWidth > columnWidths.get(colIdx)) {
|
|
|
- columnWidths.put(colIdx, perColWidth);
|
|
|
+ cell.setCellStyle(styleCache.get(styleKey));
|
|
|
+
|
|
|
+ // 记录列宽(优先使用HTML中的宽度)
|
|
|
+ if (td.hasAttr("width")) {
|
|
|
+ String widthStr = td.attr("width").replace("px", "");
|
|
|
+ try {
|
|
|
+ float px = Float.parseFloat(widthStr);
|
|
|
+ float charWidth = px / 7f; // 像素转字符宽度 (1字符≈7px)
|
|
|
+
|
|
|
+ // 考虑跨列情况:总宽度分配到各列
|
|
|
+ for (int i = 0; i < colspan; i++) {
|
|
|
+ int colIdx = excelColNum + i;
|
|
|
+ float perColWidth = charWidth / colspan;
|
|
|
+
|
|
|
+ // 取最大宽度作为列宽
|
|
|
+ columnWidths.putIfAbsent(colIdx, 0f);
|
|
|
+ if (perColWidth > columnWidths.get(colIdx)) {
|
|
|
+ columnWidths.put(colIdx, perColWidth);
|
|
|
+ }
|
|
|
}
|
|
|
+ } catch (NumberFormatException ignored) {
|
|
|
}
|
|
|
- } catch (NumberFormatException ignored) {
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
- // 标记当前单元格占据的所有位置
|
|
|
- markCellsAsOccupied(occupiedCells, excelRowNum, excelColNum, rowspan, colspan);
|
|
|
-
|
|
|
- // 创建合并区域
|
|
|
- if (colspan > 1 || rowspan > 1) {
|
|
|
- CellRangeAddress region = new CellRangeAddress(
|
|
|
- excelRowNum,
|
|
|
- excelRowNum + rowspan - 1,
|
|
|
- excelColNum,
|
|
|
- excelColNum + colspan - 1
|
|
|
- );
|
|
|
-
|
|
|
- // 检查是否与现有区域重叠
|
|
|
- if (!isOverlapping(mergedRegions, region)) {
|
|
|
- sheet.addMergedRegion(region);
|
|
|
- mergedRegions.add(region);
|
|
|
- CellStyle cellStyle = styleCache.get(styleKey);
|
|
|
- mergedFrame.put(region, cellStyle);
|
|
|
- // 为合并区域设置边框(使用左上角单元格的样式)
|
|
|
-// setMergedRegionBorders(workbook, sheet, region, styleCache.get(styleKey));
|
|
|
+ // 标记当前单元格占据的所有位置
|
|
|
+ markCellsAsOccupied(occupiedCells, excelRowNum, excelColNum, rowspan, colspan);
|
|
|
+
|
|
|
+ // 创建合并区域
|
|
|
+ if (colspan > 1 || rowspan > 1) {
|
|
|
+ CellRangeAddress region = new CellRangeAddress(
|
|
|
+ excelRowNum,
|
|
|
+ excelRowNum + rowspan - 1,
|
|
|
+ excelColNum,
|
|
|
+ excelColNum + colspan - 1
|
|
|
+ );
|
|
|
+
|
|
|
+ // 检查是否与现有区域重叠
|
|
|
+ if (!isOverlapping(mergedRegions, region)) {
|
|
|
+ sheet.addMergedRegion(region);
|
|
|
+ mergedRegions.add(region);
|
|
|
+ CellStyle cellStyle = styleCache.get(styleKey);
|
|
|
+ mergedFrame.put(region, cellStyle);
|
|
|
+ // 为合并区域设置边框(使用左上角单元格的样式)
|
|
|
+ // setMergedRegionBorders(workbook, sheet, region, styleCache.get(styleKey));
|
|
|
+ }
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
- excelColNum += colspan;
|
|
|
+ excelColNum += colspan;
|
|
|
+ }
|
|
|
+ excelRowNum++;
|
|
|
}
|
|
|
- excelRowNum++;
|
|
|
}
|
|
|
// 修复合并单元格边框问题
|
|
|
fixMergedRegionBorders(workbook, sheet, mergedRegions, mergedFrame);
|