基于Java的Markdown到Word文档转换工具的实现

摘要 :本文介绍了一个用Java开发的Markdown到Word文档转换工具。该工具通过MarkdownToWordConverter类实现,利用flexmark库将Markdown内容转换为HTML,借助jsoup库规范化HTML,再通过docx4j库将处理后的HTML导入并保存为Word文档。同时,文中给出了工具的使用示例及所需依赖。

源代码 :我用夸克网盘给你分享了「基于Java的markdown文件导出为word文件工具类」,链接:https://pan.quark.cn/s/0dc91ff08fd6

关键词:Java;Markdown;Word文档;文档转换;依赖管理

操作环境:Java8,win10,idea2024,其余依赖版本请看标题四

一、引言

在文档处理场景中,将Markdown格式的文本转换为Word文档是常见需求。Markdown因其简洁的语法广泛应用于文档编写,而Word文档在办公环境中具有更好的兼容性和格式编辑功能。本文实现的工具提供了一种便捷的方式,将Markdown内容转换为Word文档,满足不同场景下的文档使用需求。

二、MarkdownToWordConverter类的设计与实现

2.1 类结构与功能概述

MarkdownToWordConverter类包含了将Markdown内容转换为Word文档所需的多个方法,涵盖了从Markdown到HTML的转换、HTML内容的规范化处理、安全XML读取器的创建、Word文档包的构建以及文档保存等核心步骤。

2.2 Markdown到HTML的转换

convertMarkdownToHtml方法负责将Markdown内容转换为HTML。它使用flexmark库中的ParserHtmlRenderer,通过MutableDataSet配置解析和渲染选项,具体实现如下:

java 复制代码
    /**
     * 将 Markdown 内容转换为 HTML 内容
     * @param markdownContent Markdown 内容
     * @return 转换后的 HTML 内容
     */
    private static String convertMarkdownToHtml(String markdownContent) {
        // 创建一个可变的数据集,用于配置解析器和渲染器的选项
        MutableDataSet options = new MutableDataSet();

        // 创建 Markdown 解析器和 HTML 渲染器
        Parser parser = Parser.builder(options).build();
        HtmlRenderer renderer = HtmlRenderer.builder(options).build();

        // 解析 Markdown 内容
        Document document = parser.parse(markdownContent);

        // 渲染为 HTML 内容
        return renderer.render(document);
    }

2.3 HTML内容的规范化与清理

convertMarkdownToWord方法在将Markdown转换为HTML后,使用jsoup库对HTML内容进行规范化处理。通过设置outputSettings,使HTML符合XML语法和XHTML转义模式,并清理多余的空行和字符,代码如下:

java 复制代码
org.jsoup.nodes.Document jsoupDoc = Jsoup.parse(htmlContent);
jsoupDoc.outputSettings().syntax(org.jsoup.nodes.Document.OutputSettings.Syntax.xml);
jsoupDoc.outputSettings().escapeMode(Entities.EscapeMode.xhtml);
htmlContent = jsoupDoc.html();
htmlContent = htmlContent.replaceAll("(?m)^[ \t]*\r?\n", "");

这里 (?m) 是多行模式,^ 匹配每行的开头,[ \t]* 匹配 0 个或多个空格或制表符,\r?\n

匹配换行符,整个正则表达式的作用是匹配每行开头的空白字符和换行符,然后将其替换为空字符串

  1. jsoupDoc.outputSettings().syntax(org.jsoup.nodes.Document.OutputSettings.Syntax.xml);

    这行代码调用 outputSettings() 方法获取 jsoupDoc 的输出设置对象,然后使用 syntax() 方法将输出语法设置为 XML。在 HTML 中,标签闭合规则相对宽松,比如

    标签可以不闭合;而在 XML 中,所有标签都必须正确闭合,像

    这样。通过将输出语法设置为 XML,能确保生成的 HTML 内容遵循严格的标签闭合规则。

  2. jsoupDoc.outputSettings().escapeMode(Entities.EscapeMode.xhtml);

    这行代码同样先调用 outputSettings() 方法获取输出设置对象,接着使用 escapeMode() 方法将字符转义模式设置为 Entities.EscapeMode.xhtml。在 HTML 里,一些特殊字符(如 <、>、& 等)需要进行转义,否则可能会导致解析错误。将转义模式设置为 xhtml 后,Jsoup 会把这些特殊字符转换为对应的 XHTML 实体引用,例如将 < 转换为 <,> 转换为 >,& 转换为 &。

2.4 创建安全的XMLReader

为了确保XML读取的安全性,使用SAXParserFactory创建XMLReader,禁用DOCTYPE声明以及外部通用实体和参数实体,代码如下:

java 复制代码
SAXParserFactory factory = SAXParserFactory.newInstance();
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
XMLReader xmlReader = factory.newSAXParser().getXMLReader();

2.5 构建与保存Word文档

利用docx4j库构建Word文档包。创建WordprocessingMLPackage,获取MainDocumentPart,通过XHTMLImporterImpl将规范化后的HTML内容导入到Word文档的Body部分,最后使用Docx4J保存文档,代码如下:

java 复制代码
WordprocessingMLPackage wordMLPackage = WordprocessingMLPackage.createPackage();
MainDocumentPart mainDocumentPart = wordMLPackage.getMainDocumentPart();
XHTMLImporterImpl XHTMLImporter = new XHTMLImporterImpl(wordMLPackage);
Body body = mainDocumentPart.getJaxbElement().getBody();
body.getContent().addAll(XHTMLImporter.convert(htmlContent, null));
Docx4J.save(wordMLPackage, new File(outputFilePath), Docx4J.FLAG_NONE);

2.6 读取Markdown文件内容

readMarkdownFromFile方法从指定路径的文件中读取Markdown内容,使用BufferedReader逐行读取并构建字符串,代码如下:

java 复制代码
public static String readMarkdownFromFile(String filePath) throws IOException {
    StringBuilder content = new StringBuilder();
    try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {
        String line;
        while ((line = reader.readLine()) != null) {
            content.append(line).append("\n");
        }
    }
    return content.toString();
}

三、工具使用示例

main方法中,通过指定Markdown文件路径和输出Word文档路径,展示了工具的使用方法。先读取Markdown文件内容,然后调用convertMarkdownToWord方法进行转换,并在控制台输出转换结果信息,代码如下:

java 复制代码
public static void main(String[] args) {
    String markdownFilePath = "C:\\Users\\admin\\Desktop\\salaries.md";
    String outputWordFilePath = "C:\\Users\\admin\\Desktop\\file.docx";
    try {
        String markdownContent = readMarkdownFromFile(markdownFilePath);
        convertMarkdownToWord(markdownContent, outputWordFilePath);
        System.out.println("Markdown 转换为 Word 文档成功!");
    } catch (Exception e) {
        System.err.println("转换过程中出现错误: " + e.getMessage());
        e.printStackTrace();
    }
}

四、依赖管理

工具的实现依赖多个Java库,包括flexmark用于Markdown解析,jsoup用于HTML处理,docx4j用于Word文档操作,以及日志、XML处理等相关库。在pom.xml文件中,详细列出了各个依赖及其版本号,如下所示:

xml 复制代码
<dependencies>
    <dependency>
        <groupId>commons-codec</groupId>
        <artifactId>commons-codec</artifactId>
        <version>1.15</version>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>2.0.44</version>
    </dependency>
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.2.3</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <version>2.7.17</version>
    </dependency>
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-lang3</artifactId>
        <version>3.14.0</version>
    </dependency>
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter-api</artifactId>
        <version>5.8.2</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter-engine</artifactId>
        <version>5.8.2</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <version>5.3.29</version>
    </dependency>
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi</artifactId>
        <version>5.2.3</version>
    </dependency>
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi-ooxml</artifactId>
        <version>5.2.3</version>
    </dependency>
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi-scratchpad</artifactId>
        <version>5.2.3</version>
    </dependency>
    <dependency>
        <groupId>org.docx4j</groupId>
        <artifactId>docx4j-JAXB-Internal</artifactId>
        <version>8.3.10</version>
    </dependency>
    <dependency>
        <groupId>org.docx4j</groupId>
        <artifactId>docx4j-ImportXHTML</artifactId>
        <version>8.3.10</version>
    </dependency>
    <dependency>
        <groupId>org.jvnet.jaxb2_commons</groupId>
        <artifactId>jaxb2-basics</artifactId>
        <version>1.11.1</version>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.36</version>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-simple</artifactId>
        <version>1.7.36</version>
    </dependency>
    <dependency>
        <groupId>com.vladsch.flexmark</groupId>
        <artifactId>flexmark</artifactId>
        <version>0.60.2</version>
    </dependency>
    <dependency>
        <groupId>org.jsoup</groupId>
        <artifactId>jsoup</artifactId>
        <version>1.16.1</version>
    </dependency>
</dependencies>

五、转化效果

对于文字的转化效果还是可以的。

至于代码块中的内容在word里显示不佳,但是单独把word中代码块的内容拿出来放到txt,或者其他代码块中格式确实正常的

复制代码
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
plt.rcParams['font.sans-serif'] = ['SimHei'] 
plt.rcParams['axes.unicode_minus'] = False 
df = pd.read_csv('E:\\pycharm_workspace\\数据集\\salaries.csv')

六、完整代码

java 复制代码
package com.example.utils;

import com.vladsch.flexmark.html.HtmlRenderer;
import com.vladsch.flexmark.parser.Parser;
import com.vladsch.flexmark.util.ast.Document;
import com.vladsch.flexmark.util.data.MutableDataSet;
import org.docx4j.Docx4J;
import org.docx4j.convert.in.xhtml.XHTMLImporterImpl;
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
import org.docx4j.openpackaging.parts.WordprocessingML.MainDocumentPart;
import org.docx4j.wml.Body;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Entities;

import java.io.*;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParserFactory;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.InputSource;

public class MarkdownToWordConverter {

    /**
     * 将 Markdown 内容转换为 Word 文档
     * @param markdownContent Markdown 内容
     * @param outputFilePath 输出的 Word 文档路径
     * @throws Exception 转换过程中可能出现的异常
     */
    public static void convertMarkdownToWord(String markdownContent, String outputFilePath) throws Exception {
        // 将 Markdown 转换为 HTML
        String htmlContent = convertMarkdownToHtml(markdownContent);

        // 使用 Jsoup 规范化 HTML 内容
        // 使用全限定类名
        org.jsoup.nodes.Document jsoupDoc = Jsoup.parse(htmlContent);
        jsoupDoc.outputSettings().syntax(org.jsoup.nodes.Document.OutputSettings.Syntax.xml);
        jsoupDoc.outputSettings().escapeMode(Entities.EscapeMode.xhtml);
        htmlContent = jsoupDoc.html();

        // 清理多余的空行和字符
        htmlContent = htmlContent.replaceAll("(?m)^[ \t]*\r?\n", "");

        // 创建安全的 XMLReader
        SAXParserFactory factory = SAXParserFactory.newInstance();
        factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
        factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
        factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
        XMLReader xmlReader = factory.newSAXParser().getXMLReader();

        // 创建 Word 文档包
        WordprocessingMLPackage wordMLPackage = WordprocessingMLPackage.createPackage();
        MainDocumentPart mainDocumentPart = wordMLPackage.getMainDocumentPart();

        // 设置 XHTML 导入器
        XHTMLImporterImpl XHTMLImporter = new XHTMLImporterImpl(wordMLPackage);

        // 将 HTML 内容导入到 Word 文档中
        Body body = mainDocumentPart.getJaxbElement().getBody();
        body.getContent().addAll(XHTMLImporter.convert(htmlContent, null));

        // 保存 Word 文档
        Docx4J.save(wordMLPackage, new File(outputFilePath), Docx4J.FLAG_NONE);
    }

    /**
     * 将 Markdown 内容转换为 HTML 内容
     * @param markdownContent Markdown 内容
     * @return 转换后的 HTML 内容
     */
    private static String convertMarkdownToHtml(String markdownContent) {
        MutableDataSet options = new MutableDataSet();

        // 创建 Markdown 解析器和 HTML 渲染器
        Parser parser = Parser.builder(options).build();
        HtmlRenderer renderer = HtmlRenderer.builder(options).build();

        // 解析 Markdown 内容
        Document document = parser.parse(markdownContent);

        // 渲染为 HTML 内容
        return renderer.render(document);
    }

    /**
     * 从文件中读取 Markdown 内容
     * @param filePath Markdown 文件路径
     * @return Markdown 内容
     * @throws IOException 读取文件时可能出现的异常
     */
    public static String readMarkdownFromFile(String filePath) throws IOException {
        StringBuilder content = new StringBuilder();
        try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {
            String line;
            while ((line = reader.readLine()) != null) {
                content.append(line).append("\n");
            }
        }
        return content.toString();
    }

    public static void main(String[] args) {
        String markdownFilePath = "C:\\Users\\admin\\Desktop\\salaries.md";
        String outputWordFilePath = "C:\\Users\\admin\\Desktop\\file.docx";

        try {
            // 读取 Markdown 文件内容
            String markdownContent = readMarkdownFromFile(markdownFilePath);

            // 将 Markdown 内容转换为 Word 文档
            convertMarkdownToWord(markdownContent, outputWordFilePath);

            System.out.println("Markdown 转换为 Word 文档成功!");
        } catch (Exception e) {
            System.err.println("转换过程中出现错误: " + e.getMessage());
            e.printStackTrace();
        }
    }
}

七、也可以顺便把html文件输出出来

java 复制代码
package com.example.utils;

import com.vladsch.flexmark.html.HtmlRenderer;
import com.vladsch.flexmark.parser.Parser;
import com.vladsch.flexmark.util.ast.Document;
import com.vladsch.flexmark.util.data.MutableDataSet;
import org.docx4j.Docx4J;
import org.docx4j.convert.in.xhtml.XHTMLImporterImpl;
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
import org.docx4j.openpackaging.parts.WordprocessingML.MainDocumentPart;
import org.docx4j.wml.Body;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Entities;

import java.io.*;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParserFactory;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.InputSource;

public class MarkdownToWordConverter {

    /**
     * 将 Markdown 内容转换为 Word 文档,并输出 HTML 文件
     * @param markdownContent Markdown 内容
     * @param outputWordFilePath 输出的 Word 文档路径
     * @param outputHtmlFilePath 输出的 HTML 文件路径
     * @throws Exception 转换过程中可能出现的异常
     */
    public static void convertMarkdownToWord(String markdownContent, String outputWordFilePath, String outputHtmlFilePath) throws Exception {
        // 将 Markdown 转换为 HTML
        String htmlContent = convertMarkdownToHtml(markdownContent);

        // 使用 Jsoup 规范化 HTML 内容
        org.jsoup.nodes.Document jsoupDoc = Jsoup.parse(htmlContent);
        jsoupDoc.outputSettings().syntax(org.jsoup.nodes.Document.OutputSettings.Syntax.xml);
        jsoupDoc.outputSettings().escapeMode(Entities.EscapeMode.xhtml);
        htmlContent = jsoupDoc.html();

        // 清理多余的空行和字符
        htmlContent = htmlContent.replaceAll("(?m)^[ \t]*\r?\n", "");

        // 输出 HTML 文件
        try (FileWriter writer = new FileWriter(outputHtmlFilePath)) {
            writer.write(htmlContent);
        }

        // 创建安全的 XMLReader
        SAXParserFactory factory = SAXParserFactory.newInstance();
        factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
        factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
        factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
        XMLReader xmlReader = factory.newSAXParser().getXMLReader();

        // 创建 Word 文档包
        WordprocessingMLPackage wordMLPackage = WordprocessingMLPackage.createPackage();
        MainDocumentPart mainDocumentPart = wordMLPackage.getMainDocumentPart();

        // 设置 XHTML 导入器
        XHTMLImporterImpl XHTMLImporter = new XHTMLImporterImpl(wordMLPackage);

        // 将 HTML 内容导入到 Word 文档中
        Body body = mainDocumentPart.getJaxbElement().getBody();
        body.getContent().addAll(XHTMLImporter.convert(htmlContent, null));

        // 保存 Word 文档
        Docx4J.save(wordMLPackage, new File(outputWordFilePath), Docx4J.FLAG_NONE);
    }

    /**
     * 将 Markdown 内容转换为 HTML 内容
     * @param markdownContent Markdown 内容
     * @return 转换后的 HTML 内容
     */
    private static String convertMarkdownToHtml(String markdownContent) {
        MutableDataSet options = new MutableDataSet();

        // 创建 Markdown 解析器和 HTML 渲染器
        Parser parser = Parser.builder(options).build();
        HtmlRenderer renderer = HtmlRenderer.builder(options).build();

        // 解析 Markdown 内容
        Document document = parser.parse(markdownContent);

        // 渲染为 HTML 内容
        return renderer.render(document);
    }

    /**
     * 从文件中读取 Markdown 内容
     * @param filePath Markdown 文件路径
     * @return Markdown 内容
     * @throws IOException 读取文件时可能出现的异常
     */
    public static String readMarkdownFromFile(String filePath) throws IOException {
        StringBuilder content = new StringBuilder();
        try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {
            String line;
            while ((line = reader.readLine()) != null) {
                content.append(line).append("\n");
            }
        }
        return content.toString();
    }

    public static void main(String[] args) {
        String markdownFilePath = "C:\\Users\\admin\\Desktop\\markdown文件\\salaries.md";
        String outputWordFilePath = "C:\\Users\\admin\\Desktop\\markdown文件\\file.docx";
        String outputHtmlFilePath = "C:\\Users\\admin\\Desktop\\markdown文件\\file.html";

        try {
            // 读取 Markdown 文件内容
            String markdownContent = readMarkdownFromFile(markdownFilePath);

            // 将 Markdown 内容转换为 Word 文档,并输出 HTML 文件
            convertMarkdownToWord(markdownContent, outputWordFilePath, outputHtmlFilePath);

            System.out.println("Markdown 转换为 Word 文档和 HTML 文件成功!");
        } catch (Exception e) {
            System.err.println("转换过程中出现错误: " + e.getMessage());
            e.printStackTrace();
        }
    }
}
相关推荐
洛小豆1 天前
为什么 Integer a = 100; 不创建新对象?从编译到运行的全流程拆解
java·后端·spring
汪不止1 天前
Spring Boot 应用启动机制详解
java·spring boot·后端
伯明翰java1 天前
mybatis-generator插件自动生成mapper及其实体模型配置
java·开发语言·mybatis
聪明的笨猪猪1 天前
Java Spring “Bean” 面试清单(含超通俗生活案例与深度理解)
java·经验分享·笔记·面试
一叶飘零_sweeeet1 天前
从 0 到 1 搭建实时数据看板:RabbitMQ+WebSocket 实战指南
java·websocket·rabbitmq·数据看板
咖啡啡不加糖1 天前
贪心算法详解与应用
java·后端·算法·贪心算法
寒月霜华1 天前
java-网络编程-UDP,TCP通信
java·网络·tcp/ip·udp
DN金猿1 天前
java8提取list中对象有相同属性值的对象或属性值
java·list·stream·java8
深色風信子1 天前
SpringBoot 集成 LangChain4j 本地调用 Ollama
java·spring boot·spring·ollama·langchain4j
卷心菜的学习路1 天前
《计算》第九十章读书笔记
java·读书笔记·编程思维