基于Java的Markdown转Word工具(标题、段落、表格、Echarts图等)

项目源于我们开发的一款基于大模型的报告生成工具。由于需要将 Markdown 格式的内容导出为 Word 文档,而市面上缺乏合适的现成工具,所以决定自己开发一个Markdown转Word的工具。

🩷源码地址:daydayup-zyn/md2doc-plus

😀实现思路

md2doc-plus 基于 Java 17 和 Apache POI 构建,采用模块化设计,主要包括以下几个核心组件:

  1. Markdown 解析器:负责解析 Markdown 内容,识别文本、表格、图表等元素
  2. 文档生成器:使用 Apache POI 创建和操作 Word 文档
  3. 模板引擎:支持动态生成文档模板,便于后续内容替换
  4. 图表转换器:专门处理 ECharts 图表到 Word 图表的转换

😃核心转换流程如下:

  • 解析 Markdown 内容,识别各种元素(标题、段落、表格、图表等)
  • 基于 Markdown 结构,自动化创建 Word 文档模板,为动态内容预留占位符
  • 将解析后的内容填充到模板中
  • 生成最终的 Word 文档

😁功能亮点

  1. 动态word模板生成

系统能够根据 Markdown 内容结构动态生成 Word 模板,为后续内容填充预留占位符。

  1. Markdown 各级标题与段落文本支持

完整支持 Markdown 的六级标题(H1-H6)以及普通段落文本的转换。系统会自动识别标题级别,并应用相应的格式化样式,包括字体大小、加粗等属性。

  1. Markdown 表格转换

能够准确解析并转换 Markdown 表格语法,将表格内容转换为 Word 中的表格对象,保持原有的行列结构和数据内容。

  1. ECharts图表集成

支持将 Markdown 中的 ECharts 图表配置代码块转换为 Word 中的图表对象,包括自动识别 ECharts 代码块、解析 ECharts 配置并提取图表数据、在 Word 文档中生成对应的图表对象、支持多种图表类型(柱状图、折线图、饼图等)。

{title: {text: '月度销售数据1'},tooltip: {trigger: 'axis'},xAxis: {type: 'category',data: ['1月', '2月', '3月', '4月', '5月', '6月']},yAxis: {type: 'value',name: '销售额'},series: [{name: '销售额',type: 'line',data: [15.32, 15.87, 14.96, 16.23, 13.21, 13.53]}]
}
  1. 章节标题自动添加序号、标题格式

具备自动化标题编号功能,能够根据标题层级自动为章节标题添加序号(如 1.1、1.2、2.1 等),并应用标准的标题样式。这使得生成的 Word 文档可以直接用于创建目录,极大地提升了文档的专业性和可用性。

🫠核心实现代码解析

  1. Markdown 解析与文档结构创建
    md2doc-plus 的核心功能之一是解析 Markdown 内容并创建相应的 Word 文档结构。这主要在 DynamicWordDocumentCreator 类中实现:

    /*** 解析Markdown内容并创建Word文档结构* @param document Word文档对象* @param markdownContent Markdown内容*/
    private static void parseAndCreateDocumentStructure(XWPFDocument document, String markdownContent) {// 用于匹配ECharts代码块的正则表达式Pattern echartsPattern = Pattern.compile("‍```echarts\\s*\\n(.*?)\\n‍```", Pattern.DOTALL);// 用于匹配表格的正则表达式Pattern tablePattern = Pattern.compile("(\\|[^\\n]*\\|\\s*\\n\\s*\\|[-|\\s]*\\|\\s*\\n(?:\\s*\\|[^\\n]*\\|\\s*\\n?)*)", Pattern.MULTILINE);// 用于匹配标题的正则表达式Pattern headerPattern = Pattern.compile("^(#{1,6})\\s+(.*)$", Pattern.MULTILINE);String[] lines = markdownContent.split("\n");int chartIndex = 1;int tableIndex = 1;for (int i = 0; i < lines.length; i++) {String line = lines[i];// 检查是否为标题Matcher headerMatcher = headerPattern.matcher(line);if (headerMatcher.find()) {int level = headerMatcher.group(1).length();String title = headerMatcher.group(2);XWPFParagraph headerParagraph = document.createParagraph();setHeaderStyle(headerParagraph, level);setHeaderParagraphStyle(headerParagraph, level);XWPFRun headerRun = headerParagraph.createRun();headerRun.setText(title);headerRun.setBold(true);headerRun.setFontFamily("宋体");// 根据标题级别设置字体大小int fontSize = 16; // 默认H3switch (level) {case 1: fontSize = 22; break; // H1case 2: fontSize = 20; break; // H2case 3: fontSize = 18; break; // H3case 4: fontSize = 16; break; // H4case 5: fontSize = 14; break; // H5case 6: fontSize = 12; break; // H6}headerRun.setFontSize(fontSize);continue;}// 检查是否为ECharts图表if (line.trim().equals("‍```echarts")) {// 查找图表代码块的结束位置StringBuilder chartCode = new StringBuilder();i++; // 移动到下一行while (i < lines.length && !lines[i].trim().equals("‍```")) {chartCode.append(lines[i]).append("\n");i++;}// 创建图表占位符XWPFParagraph chartTitleParagraph = document.createParagraph();setDefaultParagraphStyle(chartTitleParagraph); // 图表标题使用默认段落样式XWPFRun chartTitleRun = chartTitleParagraph.createRun();chartTitleRun.setText("图表 " + chartIndex + ":");chartTitleRun.setBold(true);chartTitleRun.setFontFamily("宋体");// 创建实际的图表对象try {createChartInDocument(document, "chart" + chartIndex, chartCode.toString());} catch (Exception e) {// 如果创建图表失败,至少添加占位符XWPFParagraph chartParagraph = document.createParagraph();chartParagraph.setAlignment(ParagraphAlignment.CENTER);setDefaultParagraphStyle(chartParagraph);XWPFRun chartRun = chartParagraph.createRun();chartRun.setText("${chart" + chartIndex + "}");}chartIndex++;continue;}// 检查是否为表格开始if (line.startsWith("|")) {// 收集表格的所有行StringBuilder tableMarkdown = new StringBuilder(line).append("\n");i++; // 移动到下一行while (i < lines.length && (lines[i].startsWith("|") || lines[i].trim().matches("^\\|?\\s*[-|:\\s]+\\|?\\s*$"))) {tableMarkdown.append(lines[i]).append("\n");i++;}i--; // 回退一行,因为循环会自动增加i// 创建表格占位符XWPFParagraph tableTitleParagraph = document.createParagraph();setDefaultParagraphStyle(tableTitleParagraph); // 表格标题使用默认段落样式XWPFRun tableTitleRun = tableTitleParagraph.createRun();tableTitleRun.setText("表格 " + tableIndex + ":");tableTitleRun.setBold(true);tableTitleRun.setFontFamily("宋体");XWPFParagraph tableParagraph = document.createParagraph();setDefaultParagraphStyle(tableParagraph);XWPFRun tableRun = tableParagraph.createRun();tableRun.setText("${table" + tableIndex + "}");tableIndex++;continue;}// 普通段落if (!line.trim().isEmpty()) {XWPFParagraph paragraph = document.createParagraph();setDefaultParagraphStyle(paragraph); // 内容段落使用默认样式XWPFRun run = paragraph.createRun();run.setText(line);run.setFontFamily("宋体");run.setFontSize(12); // 小四号字体}}
    }
    
  2. ECharts 图表转换
    EChartsToWordConverter 类负责将 ECharts 配置转换为 Word 图表数据:

    public static void convertEChartsToWordChart(WordParams params, String chartKey, String echartsConfig) throws IOException {try {// 预处理ECharts配置,将其转换为有效的JSON格式String jsonConfig = convertEChartsToJson(echartsConfig);JsonNode rootNode = objectMapper.readTree(jsonConfig);// 获取图表标题String title = rootNode.path("title").path("text").asText("默认标题");// 创建图表ChartTable chartTable = params.addChart(chartKey).setTitle(title);// 处理 X 轴数据JsonNode xAxisNode = rootNode.path("xAxis");if (xAxisNode.isArray()) {xAxisNode = xAxisNode.get(0); // 多个 x 轴时取第一个}if (!xAxisNode.isMissingNode()) {JsonNode xAxisData = xAxisNode.path("data");if (!xAxisData.isMissingNode()) {List<String> xAxisLabels = new ArrayList<>();for (JsonNode dataNode : xAxisData) {xAxisLabels.add(dataNode.asText());}chartTable.getXAxis().addAllData(xAxisLabels);}}// 处理 Y 轴数据和系列数据JsonNode seriesNode = rootNode.path("series");if (seriesNode.isArray()) {for (JsonNode serie : seriesNode) {String seriesName = serie.path("name").asText("数据系列");JsonNode seriesData = serie.path("data");if (!seriesData.isMissingNode() && seriesData.isArray()) {List<Number> dataValues = new ArrayList<>();for (JsonNode dataNode : seriesData) {if (dataNode.isNumber()) {dataValues.add(dataNode.numberValue());} else {dataValues.add(0);}}chartTable.newYAxis(seriesName).addAllData(dataValues);}}}// 如果有 Y 轴名称设置,更新第一个 Y 轴的标题JsonNode yAxisNode = rootNode.path("yAxis");if (yAxisNode.isArray()) {yAxisNode = yAxisNode.get(0); // 多个 y 轴时取第一个}if (!yAxisNode.isMissingNode()) {String yAxisName = yAxisNode.path("name").asText("");if (!yAxisName.isEmpty() && !chartTable.getYAxis().isEmpty()) {// 获取第一个 Y 轴并设置标题String firstKey = chartTable.getYAxis().keySet().iterator().next();chartTable.getYAxis(firstKey).setTitle(yAxisName);}}} catch (Exception e) {// 如果解析失败,创建一个默认的空图表ChartTable chartTable = params.addChart(chartKey).setTitle("默认图表标题");chartTable.getXAxis().addAllData("数据1", "数据2", "数据3");chartTable.newYAxis("默认系列").addAllData(10, 20, 30);throw new IOException("解析ECharts配置时出错: " + e.getMessage(), e);}
    }
    
  3. 表格解析
    MarkdownTableParser 类负责解析 Markdown 表格:

    public static List<List<String>> parseTable(String markdownTable) {List<List<String>> tableData = new ArrayList<>();String[] lines = markdownTable.split("\n");for (String line : lines) {line = line.trim();// 跳过分隔行(只包含|和-的行)if (line.matches("^\\|?\\s*[-|:\\s]+\\|?\\s*$") && line.contains("-")) {continue;}if (line.startsWith("|")) {line = line.substring(1);}if (line.endsWith("|")) {line = line.substring(0, line.length() - 1);}String[] cells = line.split("\\|");List<String> row = new ArrayList<>();for (String cell : cells) {row.add(cell.trim());}// 只有当行不为空时才添加到表格数据中if (!row.isEmpty() && !(row.size() == 1 && row.get(0).isEmpty())) {tableData.add(row);}}return tableData;
    }
    

🥰使用示例

使用 md2doc-plus 非常简单,只需要几行代码:

public class Test {public static void main(String[] args) throws Exception {MarkdownToWordConverter.convertMarkdownFileToWord("./markdown/未命名.md","./word/未命名_output.docx");}
}

😜效果验证

  • 原始markdown文件:

在这里插入图片描述

  • 转换后的Word文档

在这里插入图片描述

🤨存在的问题

2025-08-15 已解决
1. word章节标题样式缺失,无法自动生成目录;
2. 图表样式缺失,图表显示不全,需手动调整;
3. 标题序号缺失、标题格式缺失,不能自动列出目录;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/95643.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/95643.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Unity:PlayerPrefs笔记

写在前面&#xff1a;写本系列(自用)的目的是回顾已经学过的知识、记录新学习的知识或是记录心得理解&#xff0c;方便自己以后快速复习&#xff0c;减少遗忘。一、PlayerPrefs的基本方法1、存储相关PlayerPrefs的数据存储类似于键值对存储&#xff0c;一个键对应一个值。Unity…

SQL tutorials

SQL Literature SQL运行在资料库管理系统&#xff08;Database Management System&#xff09;&#xff0c;如MySQL&#xff0c;Postgre SQL&#xff0c;Microsoft SQL Server&#xff0c; Oracle&#xff0c;etc。 SQL练习平台&#xff1a;https://sqliteviz.com/ EXAMPLE SQL…

MySQL快速恢复数据的N种方案完全教程

目录 1. 理解MySQL数据恢复的核心逻辑 1.1 数据丢失的常见场景 1.2 MySQL的“救命稻草”:关键文件和机制 2. 方案一:利用全量备份+binlog实现点对点恢复 2.1 准备工作 2.2 恢复步骤 2.3 实战案例 3. 方案二:利用InnoDB的崩溃恢复机制 3.1 崩溃恢复的原理 3.2 恢复步…

双屏加固笔记本电脑C156-2:坚固与高效的完美融合

在当今数字化时代&#xff0c;笔记本电脑已成为人们工作和生活中不可或缺的工具。然而&#xff0c;对于一些特殊行业和恶劣环境下的应用场景&#xff0c;普通笔记本电脑往往难以满足需求。此时&#xff0c;具备坚固耐用、高性能等特点的加固笔记本电脑应运而生。鲁成伟业的双屏…

Jenkins 环境部署

下载相关软件&#xff1a;Jenkins 的安装和设置 相关工具&#xff1a; Git : Git - Downloads java 17: Java Archive Downloads - Java SE 17.0.12 and earlier python : Download Python | Python.org jenkins、jenkins.war : Jenkins 的安装和设置 将所有软件安装后&am…

如何高效解决 Java 内存泄漏问题方法论

目录 一、系统化的诊断与优化方法论 二、获取内存快照:内存泄漏的第一步 (一)自动生成 Heap Dump (二)手动生成 Heap Dump 三、导入分析工具:MAT 和 JProfiler (一)MAT (Memory Analyzer Tool) (二)JProfiler (三)自身企业工具 四、深入分析:逐步排查内存…

HarmonyOS Camera Kit 全解析:从基础拍摄到跨设备协同的实战指南

在移动应用开发中&#xff0c;相机功能往往是提升用户体验的关键模块&#xff0c;但传统相机开发面临权限管理复杂、设备兼容性差、功能实现繁琐等痛点。HarmonyOS 作为面向全场景的分布式操作系统&#xff0c;其 Camera Kit&#xff08;相机服务&#xff09;通过统一的 API 接…

运用词向量模型分辨评论

代码实现&#xff1a;import jieba import pandas as pd hp pd.read_table(优质评价.txt,encodinggbk) cp pd.read_table(差评1.txt,encodinggbk) cp_segments [] contents cp.content.values.tolist() for content in contents:results jieba.lcut(content)if len(result…

基于Apache Flink的实时数据处理架构设计与高可用性实战经验分享

基于Apache Flink的实时数据处理架构设计与高可用性实战经验分享 一、业务场景描述 在现代电商平台中&#xff0c;实时用户行为数据&#xff08;点击、浏览、购物车操作等&#xff09;对业务决策、个性化推荐和风控都至关重要。我们需要搭建一个高吞吐、低延迟且具备高可用性的…

第二十四天:虚函数与纯虚函数

虚函数&#xff08;Virtual Function&#xff09; 定义&#xff1a;在基类中使用 virtual 关键字声明的成员函数&#xff0c;允许在派生类中被重新定义&#xff08;覆盖&#xff0c;override&#xff09;。其目的是实现多态性&#xff0c;即通过基类指针或引用调用函数时&#…

uniapp微信小程序-登录页面验证码的实现(springboot+vue前后端分离)EasyCaptcha验证码 超详细

一、项目技术栈登录页面暂时涉及到的技术栈如下:前端 Vue2 Element UI Axios&#xff0c;后端 Spring Boot 2 MyBatis MySQL Redis EasyCaptcha JWT Maven后端使用IntelliJ IDEA 2024.3.5 前端使用 HBuilder X 和 微信开发者工具二、实现功能及效果图过期管理验证码有…

【Java】HashMap的详细介绍

目录 一.HashMap 1.基本概念 2.底层数据结构&#xff1a; 3.HashCode和equals方法 为什么重写HashCode方法&#xff1f; 为什么重新equals方法&#xff1f; 4.put操作 1.初始化和数组检查 2.计算索引并检查桶是否为空 3.桶不为null&#xff0c;处理哈希冲突 4.判断链…

nifi 增量处理组件

在Apache NiFi中&#xff0c;QueryDatabaseTable 是一个常用的处理器&#xff0c;主要用于从关系型数据库表中增量查询数据&#xff0c;特别适合需要定期抽取新增或更新数据的场景&#xff08;如数据同步、ETL流程&#xff09;。它的核心功能是通过跟踪指定列的最大值&#xff…

【数据可视化-90】2023 年城镇居民人均收入可视化分析:Python + pyecharts打造炫酷暗黑主题大屏

&#x1f9d1; 博主简介&#xff1a;曾任某智慧城市类企业算法总监&#xff0c;目前在美国市场的物流公司从事高级算法工程师一职&#xff0c;深耕人工智能领域&#xff0c;精通python数据挖掘、可视化、机器学习等&#xff0c;发表过AI相关的专利并多次在AI类比赛中获奖。CSDN…

Multiverse模型:突破多任务处理和硬件效率瓶颈的AI创新(上)

随着人工智能技术的快速发展&#xff0c;多模态模型成为了当前研究的热点。多模态模型的核心思想是能够同时处理和理解来自不同模态&#xff08;如文本、图像、音频等&#xff09;的数据&#xff0c;从而为模型提供更加全面的语境理解和更强的泛化能力。 杨新宇&#xff0c;卡…

OpenCV 高斯模糊降噪

# 高斯模糊处理(降噪) # 参数1: 原始图像 # 参数2: 高斯核尺寸(宽,高&#xff0c;必须为正奇数) # 其他模糊方法: # - cv.blur(): 均值模糊 # - cv.medianBlur(): 中值模糊 # - cv.bilateralFilter(): 双边滤波 blur cv.GaussianBlur(img, (7,7), cv…

常见通信协议详解:TCP、UDP、HTTP/HTTPS、WebSocket 与 RPC

在现代网络通信中&#xff0c;各种协议扮演着至关重要的角色&#xff0c;它们决定了数据如何在网络中传输、控制其可靠性、实时性与适用场景。对于开发者而言&#xff0c;理解这些常见的通信协议&#xff0c;不仅有助于更好地设计系统架构&#xff0c;还能在面对不同业务需求时…

深入解析MPLS网络中的路由器角色

一、 MPLS概述&#xff1a;标签交换的艺术 在深入角色之前&#xff0c;我们首先要理解MPLS的核心思想。传统IP路由是逐跳进行的&#xff0c;每一台路由器都需要对数据包的目的IP地址进行复杂的路由表查找&#xff08;最长匹配原则&#xff09;&#xff0c;这在网络核心层会造成…

AI的拜师学艺,模型蒸馏技术

AI的拜师学艺&#xff0c;模型蒸馏技术什么是模型蒸馏&#xff0c;模型蒸馏是一种高效的模型压缩与知识转移方法&#xff0c;通过将大型教师模型的知识精炼至小型学生模型&#xff0c;让学生模型模仿教师模型的行为和内化其知识&#xff0c;在保持模型性能的同时降低资源消耗。…

Python爬虫从入门到精通(理论与实践)

目录 1. 爬虫的魅力:从好奇心到数据宝藏 1.1 爬虫的基本流程 1.2 准备你的工具箱 2. 第一个爬虫:抓取网页标题和链接 2.1 代码实战:用requests和BeautifulSoup 2.2 代码解析 2.3 遇到问题怎么办? 3. 进阶爬取:结构化数据抓取 3.1 分析网页结构 3.2 代码实战:抓取…