杰瑞科技汇

Java Excel转HTML如何实现?

核心思路

转换过程通常分为以下几个步骤:

Java Excel转HTML如何实现?-图1
(图片来源网络,侵删)
  1. 加载 Excel 文件:使用 Java 库读取 Excel 文件,将其内容加载到内存中。
  2. 遍历工作表:Excel 文件可以包含多个工作表,通常我们按顺序处理它们。
  3. 遍历单元格:对每个工作表中的每一行、每一个单元格进行遍历。
  4. 提取样式和数据:获取单元格中的值(文本、数字、日期等)以及其样式(字体、颜色、背景、边框等)。
  5. 生成 HTML 标签:根据提取的数据和样式,构建对应的 HTML 标签(如 <table>, <tr>, <td>, <b>, <span style="..."> 等)。
  6. 写入输出文件:将生成的 HTML 字符串写入到一个 .html 文件中。

使用 Apache POI (推荐)

Apache POI 是 Java 处理 Microsoft Office 格式文件最强大、最流行的开源库,它提供了对 .xls (HSSF) 和 .xlsx (XSSF) 的完整支持。

优点

  • 功能最全面:可以精确控制 Excel 的几乎所有元素,包括样式、公式、图片、图表(需额外库)、合并单元格等。
  • 社区活跃:遇到问题容易找到解决方案。
  • 行业标准:是 Java Office 操作的事实标准。

缺点

  • API 较为底层:如果只为了转 HTML,需要编写较多代码来手动处理样式和表格结构。
  • 依赖较多:核心库本身不大,但引入图片、图表等功能会增加依赖。

实现步骤

  1. 添加 Maven 依赖

    Java Excel转HTML如何实现?-图2
    (图片来源网络,侵删)

    在你的 pom.xml 文件中添加以下依赖。poipoi-ooxml 分别用于处理 .xls.xlsx 格式。

    <dependencies>
        <!-- Apache POI Core -->
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi</artifactId>
            <version>5.2.5</version>
        </dependency>
        <!-- Apache POI for Office Open XML (e.g., .xlsx) -->
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>5.2.5</version>
        </dependency>
        <!-- SLF4J API (a logging facade) -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>2.0.9</version>
        </dependency>
        <!-- Simple implementation of SLF4J -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-simple</artifactId>
            <version>2.0.9</version>
        </dependency>
    </dependencies>
  2. 编写 Java 代码

    下面是一个完整的示例,它将处理样式(包括字体、颜色、边框、对齐方式)并输出结构良好的 HTML。

    import org.apache.poi.ss.usermodel.*;
    import org.apache.poi.xssf.usermodel.XSSFWorkbook;
    import org.apache.poi.hssf.usermodel.HSSFWorkbook;
    import java.io.*;
    import java.util.HashMap;
    java.util.Map;
    public class ExcelToHtmlConverter {
        public static void main(String[] args) {
            // 输入的 Excel 文件路径
            String excelFilePath = "input.xlsx";
            // 输出的 HTML 文件路径
            String htmlFilePath = "output.html";
            try {
                convertExcelToHtml(excelFilePath, htmlFilePath);
                System.out.println("转换成功!HTML 文件已生成于: " + htmlFilePath);
            } catch (IOException e) {
                System.err.println("转换过程中发生错误: " + e.getMessage());
                e.printStackTrace();
            }
        }
        public static void convertExcelToHtml(String excelFilePath, String htmlFilePath) throws IOException {
            // 1. 打开 Excel 文件
            try (InputStream is = new FileInputStream(excelFilePath);
                 Workbook workbook = WorkbookFactory.create(is)) {
                StringBuilder htmlBuilder = new StringBuilder();
                // 2. 构建 HTML 头部
                htmlBuilder.append("<!DOCTYPE html>\n<html>\n<head>\n")
                        .append("<meta charset=\"UTF-8\">\n")
                        .append("<title>Excel to HTML</title>\n")
                        .append("<style>\n") // 内联 CSS 样式
                        .append("  table { border-collapse: collapse; width: 100%; }\n")
                        .append("  th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }\n")
                        .append("  th { background-color: #f2f2f2; }\n")
                        .append("</style>\n")
                        .append("</head>\n<body>\n")
                        .append("<table>\n");
                // 3. 遍历所有工作表
                for (int i = 0; i < workbook.getNumberOfSheets(); i++) {
                    Sheet sheet = workbook.getSheetAt(i);
                    // 如果不是第一个工作表,添加一个标题
                    if (i > 0) {
                        htmlBuilder.append("</table>\n<h2>").append(sheet.getSheetName()).append("</h2>\n<table>\n");
                    } else {
                        htmlBuilder.append("<caption>").append(sheet.getSheetName()).append("</caption>\n");
                    }
                    // 用于缓存样式,避免重复定义
                    Map<CellStyle, String> styleCache = new HashMap<>();
                    // 4. 遍历每一行
                    for (Row row : sheet) {
                        htmlBuilder.append("<tr>\n");
                        // 5. 遍历每一个单元格
                        for (Cell cell : row) {
                            htmlBuilder.append("<td");
                            // 获取单元格样式并应用
                            CellStyle style = cell.getCellStyle();
                            String styleString = styleCache.computeIfAbsent(style, s -> {
                                StringBuilder sb = new StringBuilder();
                                // 字体
                                Font font = workbook.getFontAt(style.getFontIndexAsInt());
                                sb.append(" font-family:").append(font.getFontName()).append(";");
                                sb.append(" font-size:").append(font.getFontHeightInPoints()).append("pt;");
                                sb.append(" font-weight:").append(font.getBold() ? "bold" : "normal").append(";");
                                sb.append(" font-style:").append(font.getItalic() ? "italic" : "normal").append(";");
                                sb.append(" color:").append(getColorHex(font.getColor())).append(";");
                                // 背景色
                                if (style.getFillForegroundColor() != IndexedColors.AUTOMATIC.getIndex()) {
                                    sb.append(" background-color:").append(getColorHex(style.getFillForegroundColor())).append(";");
                                }
                                // 对齐方式
                                sb.append(" text-align:");
                                switch (style.getAlignment()) {
                                    case CENTER: sb.append("center;"); break;
                                    case RIGHT: sb.append("right;"); break;
                                    default: sb.append("left;"); break;
                                }
                                // 边框
                                sb.append(" border: 1px solid #ddd;"); // 简化边框处理
                                return sb.toString();
                            });
                            htmlBuilder.append(" style=\"").append(styleString).append("\"");
                            // 合并单元格的处理
                            if (sheet.isMergedRegion(row.getRowNum())) {
                                for (CellRangeAddress region : sheet.getMergedRegions()) {
                                    if (region.isInRange(row.getRowNum(), cell.getColumnIndex())) {
                                        int colspan = region.getLastColumn() - region.getFirstColumn() + 1;
                                        int rowspan = region.getLastRow() - region.getFirstRow() + 1;
                                        htmlBuilder.append(" colspan=\"").append(colspan).append("\"");
                                        htmlBuilder.append(" rowspan=\"").append(rowspan).append("\"");
                                        break;
                                    }
                                }
                            }
                            htmlBuilder.append(">");
                            // 6. 获取单元格值
                            switch (cell.getCellType()) {
                                case STRING:
                                    htmlBuilder.append(cell.getStringCellValue());
                                    break;
                                case NUMERIC:
                                    if (DateUtil.isCellDateFormatted(cell)) {
                                        htmlBuilder.append(cell.getDateCellValue().toString());
                                    } else {
                                        htmlBuilder.append(cell.getNumericCellValue());
                                    }
                                    break;
                                case BOOLEAN:
                                    htmlBuilder.append(cell.getBooleanCellValue());
                                    break;
                                case FORMULA:
                                    // 也可以尝试计算公式结果: cell.getNumericCellValue()
                                    htmlBuilder.append("= ").append(cell.getCellFormula());
                                    break;
                                case BLANK:
                                    // 空单元格,输出空格以保持表格结构
                                    htmlBuilder.append("&nbsp;");
                                    break;
                                default:
                                    htmlBuilder.append("N/A");
                            }
                            htmlBuilder.append("</td>\n");
                        }
                        htmlBuilder.append("</tr>\n");
                    }
                }
                // 7. 构建 HTML 尾部
                htmlBuilder.append("</table>\n</body>\n</html>");
                // 8. 写入 HTML 文件
                try (BufferedWriter writer = new BufferedWriter(new FileWriter(htmlFilePath))) {
                    writer.write(htmlBuilder.toString());
                }
            }
        }
        /**
         * 将 POI 的 IndexedColor 值转换为十六进制颜色字符串
         */
        private static String getColorHex(short indexedColor) {
            // 这是一个简化的实现,实际 POI 中有更完整的颜色映射
            // 这里只返回一个占位符,实际项目中应使用完整的颜色映射表
            return "#" + Integer.toHexString(indexedColor).substring(2);
        }
    }

使用 EasyExcel (推荐,阿里巴巴出品)

EasyExcel 是阿里巴巴开源的、基于 POI 的一个工具库,它解决了 POI 内存占用高的问题,尤其适合处理大文件,并且提供了更简洁的 API。

Java Excel转HTML如何实现?-图3
(图片来源网络,侵删)

优点

  • API 简洁:读和写的 API 都非常直观,易于上手。
  • 性能优越:采用 SAX 模式读取,内存占用极低,适合处理百万行级别的 Excel。
  • 功能强大:除了读写,也提供了模板导出、填充等高级功能。

缺点

  • 样式支持相对较弱:虽然支持,但相比 POI 原生 API,对复杂样式的控制能力稍逊一筹。
  • 主要关注读写:虽然可以转换,但其核心定位是读写工具,而不是专门的转换工具。

实现步骤

  1. 添加 Maven 依赖

    <dependencies>
        <!-- EasyExcel -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>easyexcel</artifactId>
            <version>3.3.2</version>
        </dependency>
        <!-- EasyExcel 内部依赖了 POI -->
    </dependencies>
  2. 编写 Java 代码

    EasyExcel 没有直接提供“转 HTML”的现成方法,但它提供了强大的 AnalysisEventListener(分析监听器),我们可以在监听器中处理每一行数据并构建 HTML,这种方法更灵活,但需要自己处理表格结构。

    import com.alibaba.excel.EasyExcel;
    import com.alibaba.excel.context.AnalysisContext;
    import com.alibaba.excel.event.AnalysisEventListener;
    import com.alibaba.excel.read.metadata.ReadRowHolder;
    import com.alibaba.excel.read.metadata.ReadSheet;
    import org.apache.poi.ss.usermodel.*;
    import java.io.BufferedWriter;
    java.io.FileInputStream;
    java.io.FileWriter;
    import java.io.IOException;
    import java.util.HashMap;
    java.util.Map;
    public class EasyExcelToHtmlConverter {
        public static void main(String[] args) {
            String excelFilePath = "input.xlsx";
            String htmlFilePath = "output_easyexcel.html";
            try {
                convertExcelToHtml(excelFilePath, htmlFilePath);
                System.out.println("EasyExcel 转换成功!HTML 文件已生成于: " + htmlFilePath);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        public static void convertExcelToHtml(String excelFilePath, String htmlFilePath) throws IOException {
            // 使用 POI 的 Workbook 来获取完整的样式信息,因为 EasyExcel 的监听器获取样式信息有限
            try (Workbook workbook = WorkbookFactory.create(new FileInputStream(excelFilePath))) {
                StringBuilder htmlBuilder = new StringBuilder();
                htmlBuilder.append("<!DOCTYPE html>\n<html>\n<head>\n")
                        .append("<meta charset=\"UTF-8\">\n<title>EasyExcel to HTML</title>\n")
                        .append("<style>table,td{border:1px solid #ddd;border-collapse:collapse;padding:8px;}</style>\n")
                        .append("</head>\n<body>\n<table>\n");
                Map<Integer, String> styleCache = new HashMap<>();
                // EasyExcel 读取
                EasyExcel.read(excelFilePath, new AnalysisEventListener<Map<Integer, String>>() {
                    private int currentSheetIndex = -1;
                    private int currentRowNum = -1;
                    @Override
                    public void invokeHead(Map<Integer, String> headMap, ReadRowHolder readRowHolder) {
                        // 处理表头
                        currentSheetIndex = readRowHolder.getSheetNo();
                        currentRowNum = readRowHolder.getRowNum();
                        htmlBuilder.append("<tr>\n");
                        for (int i = 0; i < headMap.size(); i++) {
                            htmlBuilder.append("<th>").append(headMap.getOrDefault(i, "")).append("</th>\n");
                        }
                        htmlBuilder.append("</tr>\n");
                    }
                    @Override
                    public void invoke(Map<Integer, String> rowData, AnalysisContext context) {
                        // 处理数据行
                        // 检查是否是新工作表的开始
                        if (context.readRowHolder().getSheetNo() != currentSheetIndex) {
                            // ... 处理新工作表的逻辑 ...
                            // 为简化,这里只处理第一个工作表
                            return;
                        }
                        // 检查是否是新行的开始
                        if (context.readRowHolder().getRowIndex() != currentRowNum + 1) {
                            // ... 处理新行的逻辑 ...
                            // 为简化,这里假设行是连续的
                        }
                        currentRowNum = context.readRowHolder().getRowIndex();
                        htmlBuilder.append("<tr>\n");
                        for (int i = 0; i < rowData.size(); i++) {
                            htmlBuilder.append("<td>").append(rowData.getOrDefault(i, "&nbsp;")).append("</td>\n");
                        }
                        htmlBuilder.append("</tr>\n");
                    }
                    @Override
                    public void doAfterAllAnalysed(AnalysisContext context) {
                        // 所有数据解析完成后的操作
                        // 这里可以添加关闭表格标签等
                    }
                }).sheet().doRead();
                htmlBuilder.append("</table>\n</body>\n</html>");
                try (BufferedWriter writer = new BufferedWriter(new FileWriter(htmlFilePath))) {
                    writer.write(htmlBuilder.toString());
                }
            }
        }
    }

注意:上面的 EasyExcel 示例为了简化,省略了对复杂样式和合并单元格的完整处理,EasyExcel 的优势在于数据读取,如果你需要高保真地转换样式,结合 POI 来获取样式信息,然后用 EasyExcel 来流式读取数据 是一个不错的折中方案。


使用商业库 (如 Aspose.Cells)

除了开源库,还有一些商业库提供非常强大和简单的转换功能。

代表库Aspose.Cells for Java

优点

  • API 极其简单:通常一行代码就能完成转换。
  • 保真度极高:能完美还原 Excel 的外观,包括复杂的样式、图表、图片、页眉页脚等。
  • 性能和稳定性好:商业库通常经过严格测试,性能优异。

缺点

  • 收费:需要购买许可证,对于个人或小项目可能成本较高。
  • 闭源:无法查看源码,定制化能力受限。

示例代码 (Aspose.Cells)

import com.aspose.cells.*;
public class AsposeExcelToHtml {
    public static void main(String[] args) throws Exception {
        // 加载 Excel 文件
        Workbook workbook = new Workbook("input.xlsx");
        // 创建 HtmlSaveOptions 对象
        HtmlSaveOptions options = new HtmlSaveOptions();
        // 可以设置一些选项,例如将 CSS 样式内联
        options.setCssStyles(CssStylesType.INLINE);
        // 保存为 HTML
        workbook.save("output_aspose.html", options);
        System.out.println("Aspose.Cells 转换成功!");
    }
}

可以看到,使用 Aspose.Cells 的代码量非常少,功能非常强大。


总结与选择建议

特性 Apache POI EasyExcel Aspose.Cells (商业)
易用性 中等 (需要较多代码) 高 (API 简洁) 极高 (一行代码)
功能/保真度 极高 (完全控制) 中等 (样式支持较弱) 极高 (完美还原)
性能 一般 (大文件内存占用高) 极高 (流式读取) 优秀
成本 免费 免费 收费
适用场景 需要精细控制样式、处理复杂 Excel 结构、免费项目 大文件处理、数据读写为主、免费项目 追求极致便利和保真度、预算充足的项目

如何选择?

  • 如果你是个人开发者或在做小型项目,且需要免费方案

    • 如果你的 Excel 文件不大,且需要精确控制样式和布局,选择 Apache POI,虽然代码多一点,但最灵活、最可靠。
    • 如果你的 Excel 文件非常大(几十MB甚至GB级别),选择 EasyExcel,它能有效防止内存溢出。
  • 如果你是公司项目,预算充足,且追求开发效率和转换质量

    • 毫不犹豫选择 Aspose.Cells,它能为你节省大量开发和测试时间,转换效果也最好。

对于大多数初学者和中小型项目,从 Apache POI 开始学习是最好的选择,因为它能让你深刻理解 Excel 到 HTML 的转换原理。

分享:
扫描分享到社交APP
上一篇
下一篇