Java生成Markdown格式内容

news2024/9/28 11:54:30

         前一篇写的是markdown格式的文本内容转换保存为word文档,是假定已经有一个现成的markdown格式的文本,然后直接转换保存为word文档,不过在开发中,通常情况下,数据是从数据库中获取,拿到的数据映射到java对象上,这一篇就是处理如何将java对象数据生成为markdown文本

1.首先编写一个markdown的语法生成的处理类:

package com.xiaomifeng1010.common.markdown;

import org.apache.commons.lang3.StringUtils;

import java.util.*;

/**
 * @author xiaomifeng1010
 * @version 1.0
 * @date: 2024-09-21 20:50
 * @Description
 */
public class MarkdownHandler {

    // ~ APIs
    // -----------------------------------------------------------------------------------------------------------------
    public static SectionBuilder of() {
        return new SectionBuilder(new Section(Section.Type.NORMAL, null, null, null, 0));
    }

    // ~ public classes & public constants & public enums
    // -----------------------------------------------------------------------------------------------------------------
    public enum Style {
        NORMAL("normal"), BOLD("bold"), ITALIC("italic"),
        RED("red"), GREEN("green"), GRAY("gray"), YELLOW("gold"), BLUE("blue");

        private final String name;

        Style(String name) {
            this.name = name;
        }

        public String getName() {
            return name;
        }
    }

    public static class Fonts {
        public static final Fonts EMPTY = Fonts.of("");
        private final String text;
        // ~ private fields
        // -------------------------------------------------------------------------------------------------------------
        private Set<Style> styles = Collections.emptySet();

        private Fonts(String text, Style... style) {
            this.text = text != null ? text : "";
            if (style != null) {
                this.styles = new HashSet<>(Arrays.asList(style));
            }
        }

        // ~ public methods
        // -------------------------------------------------------------------------------------------------------------
        public static Fonts of(String text) {
            return new Fonts(text, Style.NORMAL);
        }

        public static Fonts of(String text, Style... style) {
            return new Fonts(text, style);
        }

        public boolean isEmpty() {
            return this.text == null || this.text.isEmpty();
        }

        @Override
        public String toString() {
            if (styles.contains(Style.NORMAL)) {
                return text;
            }
            String last = text;
            for (Style style : styles) {
                last = parseStyle(last, style);
            }
            return last;
        }

        // ~ private methods
        // -------------------------------------------------------------------------------------------------------------
        private String parseStyle(String text, Style style) {
            if (text == null || style == null) {
                return text;
            }
            switch (style) {
                case NORMAL:
                    break;
                case BOLD:
                    return "**" + text + "**";
                case ITALIC:
                    return "*" + text + "*";
                case RED:
                case GREEN:
                case BLUE:
                case YELLOW:
                    return "<font color='" + style.getName() + "'>" + text + "</font>";
            }
            return text;
        }
    }

    /**
     * 代表一行,可以是一个普通文本或一个K-V(s)数据
     */
    public static class MetaData {
        // ~ public constants
        // -------------------------------------------------------------------------------------------------------------
        public static final String DEFAULT_SEPARATOR = ":";
        public static final String DEFAULT_VALUE_SEPARATOR = " | ";
        public static final String LINK_TEMPLATE = "[%s▸](%s)";

        // ~ private fields
        // -------------------------------------------------------------------------------------------------------------
        private final Type type;
        private final Fonts text;
        private final Collection<Fonts> values;
        private final String separator = DEFAULT_SEPARATOR;
        private final String valueSeparator = DEFAULT_VALUE_SEPARATOR;

        public MetaData(Fonts text) {
            this(text, null);
        }

        public MetaData(Type type) {
            this(type, null, null);
        }

        public MetaData(Fonts text, Collection<Fonts> values) {
            this(Type.NORMAL, text, values);
        }

        public MetaData(Type type, Fonts text, Collection<Fonts> values) {
            this.type = type;
            this.text = text;
            this.values = values;
        }

        @Override
        public String toString() {
            return generateString(this.valueSeparator);
        }

        /**
         * generate one line
         */
        private String generateString(String valueSeparator) {
            boolean hasValues = values != null && !values.isEmpty();
            boolean hasText = text != null && !text.isEmpty();
            StringJoiner joiner = new StringJoiner(valueSeparator);
            String ret = "";
            switch (type) {
                case NORMAL:
                    if (hasText && hasValues) {
                        values.forEach(v -> joiner.add(v.toString()));
                        ret = text + separator + joiner;
                    } else if (!hasText && hasValues) {
                        values.forEach(v -> joiner.add(v.toString()));
                        ret = joiner.toString();
                    } else if (hasText) {
                        ret = text.toString();
                    }
                    break;
                case LINK:
                    if (hasText && hasValues) {
                        Fonts fonts = values.stream().findFirst().orElse(null);
                        if (fonts == null) {
                            break;
                        }
                        ret = String.format(LINK_TEMPLATE, text, fonts);
                    } else if (!hasText && hasValues) {
                        Fonts url = values.stream().findFirst().orElse(null);
                        if (url == null) {
                            break;
                        }
                        ret = String.format(LINK_TEMPLATE, url, url);
                    } else if (hasText) {
                        ret = String.format(LINK_TEMPLATE, text, text);
                    }
                    break;
                case LINK_LIST:
                    if (hasText && hasValues) {
                        ret = text + separator + generateLinkList(values);
                    } else if (!hasText && hasValues) {
                        ret = generateLinkList(values);
                    } else if (hasText) {
                        ret = String.format(LINK_TEMPLATE, text, text);
                    }
                    break;
                case BR:
                    ret = "<br>";
            }
            return ret;
        }

        // ~ private methods
        // -------------------------------------------------------------------------------------------------------------

        private String generateLinkList(Collection<Fonts> values) {
            if (values == null || values.isEmpty()) {
                return "";
            }
            Object[] valueArr = values.toArray();
            StringJoiner linkList = new StringJoiner(valueSeparator);
            for (int i = 0; i + 1 < valueArr.length; i += 2) {
                linkList.add(String.format(LINK_TEMPLATE, valueArr[i], valueArr[i + 1]));
            }
            boolean isPairNum = (valueArr.length % 2) == 0;
            if (!isPairNum) {
                String lastUrl = valueArr[valueArr.length - 1].toString();
                linkList.add(String.format(LINK_TEMPLATE, lastUrl, lastUrl));
            }
            return linkList.toString();
        }

        private enum Type {
            /** only plain text, plain text list with a name */
            NORMAL,
            /**
             * text : link name
             * values: index 0 is URL if existed.
             */
            LINK, LINK_LIST,
            BR,
        }
    }

    // ~ private class & private implements
    // -----------------------------------------------------------------------------------------------------------------
    private static class Section {
        private final int depth;
        private Type type;
        private Object data;
        private Section parent;
        private List<Section> children;

        private Section(Type type, Object data, Section parent, List<Section> children, int depth) {
            this.type = type;
            this.data = data;
            this.parent = parent;
            this.children = children;
            this.depth = depth;
        }

        // ~ public methods
        // -------------------------------------------------------------------------------------------------------------
        public void addChild(Section child) {
            lazyInitChildren();
            children.add(child);
        }

        public boolean childIsEmpty() {
            return children == null || children.isEmpty();
        }

        // ~ private methods
        // -------------------------------------------------------------------------------------------------------------
        private StringBuilder parse(StringBuilder latestData) {
            switch (type) {
                case LINK:
                case NORMAL:
                    latestData.append('\n').append(parseData(""));
                    return latestData;
                case BIG_TITLE:
                    latestData.append('\n').append(parseData("## "));
                    return latestData;
                case TITLE:
                    latestData.append('\n').append(parseData("### "));
                    return latestData;
                case SUBTITLE:
                    latestData.append('\n').append(parseData("#### "));
                    return latestData;
                case REF:
                    return parseRefSection(latestData);
                case CODE:
                    StringBuilder codeBlock = new StringBuilder(latestData.length() + 10);
                    codeBlock.append("\n```").append(latestData).append("\n```");
                    return codeBlock;
                case ORDER_LIST:
                    return parseOrderListSection(latestData);
                case UN_ORDER_LIST:
                    return parseUnOrderListSection(latestData);
                case TABLE:
                    return parseTableSection(latestData);
                case BR:
                    return latestData.append(parseData(""));
            }
            return latestData;
        }

        private String parseData(String prefix) {
            if (data == null) {
                return "";
            }
            return prefix + data;
        }

        private StringBuilder parseRefSection(StringBuilder latestData) {
            char[] chars = latestData.toString().toCharArray();
            if (chars.length <= 0) {
                return latestData;
            }
            StringBuilder data = new StringBuilder(chars.length * 2);
            if (chars[0] != '\n') {
                data.append("> ");
            }
            char last = 0;
            for (char c : chars) {
                if (last == '\n') {
                    data.append("> ");
                }
                data.append(c);
                last = c;
            }
            return data;
        }

        private StringBuilder parseOrderListSection(StringBuilder latestData) {
            char[] chars = latestData.toString().toCharArray();
            if (chars.length <= 0) {
                return latestData;
            }
            StringBuilder data = new StringBuilder(chars.length * 2);
            String padding = String.join("", Collections.nCopies(depth * 4, " "));
            int order = 1;
            if (chars[0] != '\n') {
                data.append(padding).append(order++).append(". ");
            }
            char last = 0;
            for (char c : chars) {
                if (last == '\n' && c != '\n' && c != ' ') {
                    data.append(padding).append(order++).append(". ");
                }
                data.append(c);
                last = c;
            }
            return data;
        }

        private StringBuilder parseUnOrderListSection(StringBuilder latestData) {
            char[] chars = latestData.toString().toCharArray();
            if (chars.length <= 0) {
                return latestData;
            }
            StringBuilder data = new StringBuilder(chars.length * 2);
            String padding = String.join("", Collections.nCopies(depth * 4, " "));
            if (chars[0] != '\n') {
                data.append(padding).append("- ");
            }
            char last = 0;
            for (char c : chars) {
                if (last == '\n' && c != '\n' && c != ' ') {
                    data.append(padding).append("- ");
                }
                data.append(c);
                last = c;
            }
            return data;
        }

        private StringBuilder parseTableSection(StringBuilder latestData) {
            if (data != null) {
                Object[][] tableData = (Object[][]) data;
                if (tableData.length > 0 && tableData[0].length > 0) {
                    StringJoiner titles = new StringJoiner(" | "), extras = new StringJoiner(" | ");
                    for (Object t : tableData[0]) {
                        titles.add(t != null ? t.toString() : "");
                        extras.add("-");
                    }
                    latestData.append("\n\n").append(titles).append('\n').append(extras);
                    for (int i = 1; i < tableData.length; i++) {
                        StringJoiner dataJoiner = new StringJoiner(" | ");
                        for (int j = 0; j < tableData[i].length; j++) {
                            dataJoiner.add(tableData[i][j] != null ? tableData[i][j].toString() : "");
                        }
                        latestData.append('\n').append(dataJoiner);
                    }
                }
            }
            return latestData.append('\n');
        }

        private void lazyInitChildren() {
            if (children == null) {
                children = new ArrayList<>();
            }
        }

        // ~ getter & setter
        // -------------------------------------------------------------------------------------------------------------
        public Type getType() {
            return type;
        }

        public void setType(Type type) {
            this.type = type;
        }

        public Object getData() {
            return data;
        }

        public void setData(Object data) {
            this.data = data;
        }

        public Section getParent() {
            return parent;
        }

        public void setParent(Section parent) {
            this.parent = parent;
        }

        public List<Section> getChildren() {
            return children;
        }

        public void setChildren(List<Section> children) {
            this.children = children;
        }

        public int getDepth() {
            return depth;
        }

        private enum Type {
            /**
             * data is {@link MetaData} and plain text
             */
            NORMAL,

            /**
             * data is {@link MetaData} and h2
             */
            BIG_TITLE,

            /**
             * data is {@link MetaData} and h3
             */
            TITLE,

            /**
             * data is {@link MetaData} and h4
             */
            SUBTITLE,

            /**
             * data is {@code null}, content is children
             */
            REF,

            /**
             * data is {@code null}, content is children
             */
            CODE,

            /**
             * data is matrix, aka String[][]
             */
            TABLE,

            /**
             * data is {@code null}, content is children
             */
            ORDER_LIST,

            /**
             * data is {@code null}, content is children
             */
            UN_ORDER_LIST,

            /**
             * data is {@link MetaData}
             */
            LINK,

            BR
        }
    }

    public static class SectionBuilder {
        private static final MdParser parser = new MdParser();
        /**
         * first is root
         */
        private final Section curSec;
        /**
         * code, ref curr -> par
         */
        private Section parentSec;
        /**
         * init null
         */
        private SectionBuilder parentBuilder;

        private SectionBuilder(Section curSec) {
            this.curSec = curSec;
        }

        private SectionBuilder(Section curSec, Section parentSec, SectionBuilder parentBuilder) {
            this.curSec = curSec;
            this.parentSec = parentSec;
            this.parentBuilder = parentBuilder;
        }

        // ~ public methods
        // -------------------------------------------------------------------------------------------------------------
        public SectionBuilder text(String text) {
            return text(text, (String) null);
        }

        public SectionBuilder text(String name, String value) {
            if (name != null) {
                Collection<Fonts> values
                        = value != null ? Collections.singletonList(Fonts.of(value)) : Collections.emptyList();
                curSec.addChild(new Section(Section.Type.NORMAL,
                        new MetaData(MetaData.Type.NORMAL, Fonts.of(name, (Style) null), values),
                        curSec, null, curSec.getDepth()));
            }
            return this;
        }

        public SectionBuilder text(String text, Style... style) {
            if (text != null) {
                curSec.addChild(new Section(Section.Type.NORMAL, new MetaData(Fonts.of(text, style)), curSec,
                        null, curSec.getDepth()));
            }
            return this;
        }

        public SectionBuilder text(Collection<String> values) {
            if (values != null && !values.isEmpty()) {
                text(null, values);
            }
            return this;
        }

        public SectionBuilder text(String name, Collection<String> values) {
            if (values == null || values.size() <= 0) {
                return text(name);
            }
            return text(name, null, values);
        }

        public SectionBuilder text(String name, Style valueStyle, Collection<String> values) {
            if (values == null || values.size() <= 0) {
                return text(name);
            }
            if (valueStyle == null) {
                valueStyle = Style.NORMAL;
            }
            List<Fonts> ele = new ArrayList<>(values.size());
            for (String value : values) {
                ele.add(Fonts.of(value, valueStyle));
            }
            curSec.addChild(new Section(Section.Type.NORMAL, new MetaData(Fonts.of(name), ele), curSec, null,
                    curSec.getDepth()));
            return this;
        }

        public SectionBuilder bigTitle(String title) {
            if (StringUtils.isNotBlank(title)) {
                curSec.addChild(new Section(Section.Type.BIG_TITLE, new MetaData(Fonts.of(title)), curSec,
                        null, curSec.getDepth()));
            }
            return this;
        }

        public SectionBuilder title(String title) {
            return title(title, Style.NORMAL);
        }

        public SectionBuilder title(String title, Style color) {
            if (StringUtils.isNotBlank(title)) {
                curSec.addChild(new Section(Section.Type.TITLE, new MetaData(Fonts.of(title, color)),
                        curSec, null, curSec.getDepth()));
            }
            return this;
        }

        public SectionBuilder title(String title, Fonts... label) {
            return title(title, null, label);
        }

        public SectionBuilder title(String title, Style titleColor, Fonts... label) {
            if (StringUtils.isNotBlank(title)) {
                if (titleColor == null) {
                    titleColor = Style.NORMAL;
                }
                List<Fonts> labelList = label != null ? Arrays.asList(label) : Collections.emptyList();
                curSec.addChild(new Section(Section.Type.TITLE, new MetaData(Fonts.of(title, titleColor), labelList),
                        curSec, null, curSec.getDepth()));
            }
            return this;
        }

        public SectionBuilder subTitle(String title) {
            if (StringUtils.isNotBlank(title)) {
                curSec.addChild(new Section(Section.Type.SUBTITLE, new MetaData(Fonts.of(title)),
                        curSec, null, curSec.getDepth()));
            }
            return this;
        }

        public SectionBuilder ref() {
            Section refSection = new Section(Section.Type.REF, null, curSec, new ArrayList<>(), curSec.getDepth());
            curSec.addChild(refSection);
            return new SectionBuilder(refSection, curSec, this);
        }

        public SectionBuilder endRef() {
            return this.parentBuilder != null ? this.parentBuilder : this;
        }

        public TableDataBuilder table() {
            return new TableDataBuilder(curSec, this);
        }

        public SectionBuilder link(String url) {
            return link(null, url);
        }

        public SectionBuilder link(String name, String url) {
            if (StringUtils.isBlank(name)) {
                name = url;
            }
            if (StringUtils.isNotBlank(url)) {
                MetaData links = new MetaData(MetaData.Type.LINK, Fonts.of(name),
                        Collections.singletonList(Fonts.of(url)));
                curSec.addChild(new Section(Section.Type.NORMAL, links, curSec, null, curSec.getDepth()));
            }
            return this;
        }

        public SectionBuilder links(Map<String, String> urlMappings) {
            return links(null, urlMappings);
        }

        public SectionBuilder links(String name, Map<String, String> urlMappings) {
            if (urlMappings != null && !urlMappings.isEmpty()) {
                List<Fonts> serialUrlInfos = new ArrayList<>();
                for (Map.Entry<String, String> entry : urlMappings.entrySet()) {
                    String key = entry.getKey();
                    String value = entry.getValue();
                    serialUrlInfos.add(Fonts.of(key != null ? key : ""));
                    serialUrlInfos.add(Fonts.of(value != null ? value : ""));
                }
                Fonts wrappedName = StringUtils.isNotBlank(name) ? Fonts.of(name) : Fonts.EMPTY;
                MetaData linksGroup = new MetaData(MetaData.Type.LINK_LIST, wrappedName, serialUrlInfos);
                curSec.addChild(new Section(Section.Type.NORMAL, linksGroup, curSec, null, curSec.getDepth()));
            }
            return this;
        }

        public SectionBuilder ol() {
            int depth = (curSec.getType() == Section.Type.ORDER_LIST || curSec.getType() == Section.Type.UN_ORDER_LIST)
                    ? curSec.getDepth() + 1
                    : curSec.getDepth();
            Section OrderListSec = new Section(Section.Type.ORDER_LIST, null, curSec, new ArrayList<>(), depth);
            curSec.addChild(OrderListSec);
            return new SectionBuilder(OrderListSec, curSec, this);
        }

        public SectionBuilder endOl() {
            return this.parentBuilder != null ? this.parentBuilder : this;
        }

        public SectionBuilder ul() {
            int depth = (curSec.getType() == Section.Type.ORDER_LIST || curSec.getType() == Section.Type.UN_ORDER_LIST)
                    ? curSec.getDepth() + 1
                    : curSec.getDepth();
            Section unOrderListSec = new Section(Section.Type.UN_ORDER_LIST, null, curSec, new ArrayList<>(), depth);
            curSec.addChild(unOrderListSec);
            return new SectionBuilder(unOrderListSec, curSec, this);
        }

        public SectionBuilder endUl() {
            return this.parentBuilder != null ? this.parentBuilder : this;
        }

        public SectionBuilder code() {
            Section codeSec = new Section(Section.Type.CODE, null, curSec, new ArrayList<>(), curSec.getDepth());
            curSec.addChild(codeSec);
            return new SectionBuilder(codeSec, curSec, this);
        }

        public SectionBuilder endCode() {
            return this.parentBuilder != null ? this.parentBuilder : this;
        }

        public SectionBuilder br() {
            curSec.addChild(new Section(Section.Type.BR, new MetaData(MetaData.Type.BR), parentSec, null,
                    curSec.getDepth()));
            return this;
        }

        public String build() {
            return parser.parse(curSec);
        }
    }

    public static class TableDataBuilder {
        private final Section parentSec;
        private final SectionBuilder parentBuilder;
        private Object[][] tableData;

        private TableDataBuilder(Section parentSec, SectionBuilder parentBuilder) {
            this.parentSec = parentSec;
            this.parentBuilder = parentBuilder;
        }

        // ~ public methods
        // -------------------------------------------------------------------------------------------------------------
        public TableDataBuilder data(Object[][] table) {
            if (table != null && table.length > 0 && table[0].length > 0) {
                tableData = table;
            }
            return this;
        }

        public TableDataBuilder data(Object[] title, Object[][] data) {
            if (title == null && data != null) {
                return data(data);
            }
            if (data != null && data.length > 0 && data[0].length > 0) {
                int minCol = Math.min(title.length, data[0].length);
                tableData = new Object[data.length + 1][minCol];
                tableData[0] = Arrays.copyOfRange(title, 0, minCol);
                for (int i = 0; i < data.length; i++) {
                    tableData[i + 1] = Arrays.copyOfRange(data[i], 0, minCol);
                }
            }
            return this;
        }

        public SectionBuilder endTable() {
            parentSec.addChild(new Section(Section.Type.TABLE, tableData, parentSec, null, parentSec.getDepth()));
            return parentBuilder;
        }
    }

    private static class MdParser {
        // ~ public methods
        // -------------------------------------------------------------------------------------------------------------
        public String parse(Section sec) {
            Section root = findRoot(sec);
            return doParse(root, root).toString().trim();
        }

        // ~ private methods
        // -------------------------------------------------------------------------------------------------------------
        private Section findRoot(Section sec) {
            if (sec.getParent() == null) {
                return sec;
            }
            return findRoot(sec.getParent());
        }

        private StringBuilder doParse(Section cur, Section root) {
            if (cur == null) {
                return null;
            }
            if (cur.childIsEmpty()) {
                return cur.parse(new StringBuilder());
            }
            StringBuilder childData = new StringBuilder();
            for (Section child : cur.getChildren()) {
                StringBuilder part = doParse(child, root);
                if (part != null) {
                    childData.append(part);
                }
            }
            return cur.parse(childData).append(cur.getParent() == root ? '\n' : "");
        }
    }
}

有了通用的markdown语法生成处理类,则可以根据项目要求,再次封装需要生成的对应的文档中的各类元素对象,比如生成有序列表,无序列表,文档首页标题,副标题,链接,图像,表格等

2. 所以再写一个生成类,里边附带了测试方法

package com.xiaomifeng1010.common.markdown.todoc;

import com.google.common.collect.Lists;
import com.ruoyi.common.markdown.MarkdownHandler;
import com.ruoyi.common.utils.MarkdownUtil;
import lombok.Data;
import org.apache.commons.collections4.CollectionUtils;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * @author xiaomifeng1010
 * @version 1.0
 * @date: 2024-09-27 18:08
 * @Description
 */
public class JavaToMarkdownGenerator {



    public static void main(String[] args) {
        String theWholeMarkdownContent = "";
//        生成一个简单的多元素的word文档
        JavaToMarkdownGenerator javaToMarkdownGenerator = new JavaToMarkdownGenerator();
//        首页大标题
        String title = javaToMarkdownGenerator.generateTitle("经济环境分析\n");
//        首页目录
        String catalog = javaToMarkdownGenerator.generatecatalog();
//        插入项目本地的logo图片
        String imgPath = JavaToMarkdownGenerator.class.getResource("/static/canton.jpg").getPath();
        String logo = javaToMarkdownGenerator.resloveImg(imgPath, "logo");
        theWholeMarkdownContent = title + catalog + logo;
//        插入正文
        List<List<String>> dataList = new ArrayList<>();
//        java实践中一般是一个java对象,从数据库查询出来的一个list集合,需要循环获取对象,然后添加到dataList中
//        模拟数据库中查询出来数据
        Employee employee = new Employee();
        List<Employee> employeeList = employee.getEmployees();
        if (CollectionUtils.isNotEmpty(employeeList)) {
            for (Employee employee1 : employeeList) {
                List<String> list = new ArrayList<>();
                list.add(employee1.getName());
                list.add(employee1.getSex());
                list.add(employee1.getAge());
                list.add(employee1.getHeight());
                dataList.add(list);

            }
            String firstTable= javaToMarkdownGenerator.generateTable(dataList, "表格1","姓名", "姓别", "芳龄", "身高");
            theWholeMarkdownContent=theWholeMarkdownContent + firstTable;
        }


//        直接拼接一段富文本,因为网页上新增填写内容的时候,有些参数输入是使用的markdown富文本编辑器
        String markdownContent = "\n# 一级标题\n" +
                "## 二级标题\n" +
                "### 三级标题\n" +
                "#### 四级标题\n" +
                "##### 五级标题\n" +
                "###### 六级标题\n" +
                "## 段落\n" +
                "这是一段普通的段落。\n" +
                "## 列表\n" +
                "### 无序列表\n" +
                "- 项目1\n" +
                "- 项目2\n" +
                "- 项目3\n" +
                "### 有序列表\n" +
                "1. 项目1\n" +
                "2. 项目2\n" +
                "3. 项目3\n" +
                "## 链接\n" +
                "[百度](https://www.baidu.com)\n" +
                "## 图片\n" +
                "![图片描述](https://www.baidu.com/img/bd_logo1.png)\n" +
                "## 表格\n" +
                "| 表头1 | 表头2 | 表头3 |\n" +
                "|-------|-------|-------|\n" +
                "| 单元格1 | 单元格2 | 单元格3 |\n" +
                "| 单元格4 | 单元格5 | 单元格6 |";
        theWholeMarkdownContent=theWholeMarkdownContent+markdownContent;

        MarkdownUtil.toDoc(theWholeMarkdownContent,"test225");
        System.out.println(catalog);
    }



    /**
     * 使用markdown的有序列表实现生成目录效果
     * @return
     */
    public String generatecatalog(){
        String md = MarkdownHandler.of()
                .title("目录")
//                不要加ref方法,可以正常生成markdown文本,但是在转换成word内容的时候,commonmark会报错
//                org.commonmark.node.OrderedList cannot be cast to org.commonmark.node.Paragra
                    .ol()
                        .text("文档介绍")
                    .endOl()
                    .ol()
                        .text("经济环境")
                            .ol()
                                .text("1.1 全球化背景")
                                .text("1.2 通缩问题产生的原因")
                                .text("1.3 如何应对通缩")
                                .text("1.4 国家实施的财政政策和货币政策")
                            .endOl()
                    .endOl()
                    .ol()
                        .text("失业问题")
                            .ol()
                                .text("2.1 失业率的概念")
                                .text("2.2 如何统计失业率")
                                .text("2.3 如何提升就业率")
                            .endOl()
                    .endOl()
                    .ol()
                        .text("理财投资")
                            .ol()
                                .text("3.1 理财投资的重要性")
                                .text("3.2 如何选择理财投资产品")
                                .text("3.3 理财投资的风险管理")
                            .endOl()
                    .endOl()
                .build();
        return md;
        }
        
/**
* 生成表格
* @paramJataList
    @paramtableHead
    @return
    **/
           
    public String generateTable(List<List<String>> datalist, String tableTitle, String... tableHead){
//        添加表头(表格第一行,列标题)
        datalist.add( 0, Arrays.asList(tableHead));
        String[][] data=datalist.stream().map(list->list.toArray(new String[0])).toArray(String[][]::new);
        String markdownContent = MarkdownHandler.of()
                .title(tableTitle)
                .table()
                .data(data)
                .endTable()
                .build();
        return markdownContent;
    }

    /**
     * 文档首页大标题效果
     * @param title
     * @return
     */
    public String generateTitle(String title){
        return MarkdownHandler.of().bigTitle(title).build();
    }


    /**
     * 处理图片
     * @param imgPath
     * @param imgName
     * @return
     */
    String resloveImg(String imgPath,String imgName){
        return "!["+imgName+"]("+imgPath+")";
    }

}

@Data
class Employee{
    private String name;
    private String sex;
    private String age;
//    身高
    private String height;
//    体重
    private String weight;
//    籍贯
    private String nativePlace;
//    职位
    private String position;
//    薪资
    private String salary;

    /**
     * 模拟从数据库中查出多条数据
     * @return
     */
    public List<Employee> getEmployees(){
        List<Employee> employees = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            Employee employee = new Employee();
            employee.setName("张三" + i);
            employee.setSex("男");
            employee.setAge("18" + i);
            employee.setHeight("180" + i);
            employee.setWeight("70" + i);
            employee.setNativePlace("北京" + i);
            employee.setPosition("java开发" + i);
            employee.setSalary("10000" + i);
            employees.add(employee);
        }
        return employees;


    }
}

测试main方法会调用MarkdownUtil,用于生成word文档保存在本地,或者通过网络进行下载保存。

3.MarkdownUtil工具类:

package com.xiaomifeng1010.common.utils;

import com.deepoove.poi.XWPFTemplate;
import com.deepoove.poi.config.Configure;
import com.deepoove.poi.plugin.markdown.MarkdownRenderData;
import com.deepoove.poi.plugin.markdown.MarkdownRenderPolicy;
import com.deepoove.poi.plugin.markdown.MarkdownStyle;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
import org.commonmark.node.Node;
import org.commonmark.parser.Parser;
import org.commonmark.renderer.html.HtmlRenderer;

import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;

/**
 * @author xiaomifeng1010
 * @version 1.0
 * @date: 2024-08-24 17:23
 * @Description
 */
@UtilityClass
@Slf4j
public class MarkdownUtil {


    /**
     * markdown转html
     *
     * @param markdownContent
     * @return
     */
    public String markdownToHtml(String markdownContent) {
        Parser parser = Parser.builder().build();
        Node document = parser.parse(markdownContent);
        HtmlRenderer renderer = HtmlRenderer.builder().build();
        String htmlContent = renderer.render(document);
        log.info(htmlContent);
        return htmlContent;
    }


    /**
     * 将markdown格式内容转换为word并保存在本地
     *
     * @param markdownContent
     * @param outputFileName
     */
    public void toDoc(String markdownContent, String outputFileName) {
        log.info("markdownContent:{}", markdownContent);
        MarkdownRenderData code = new MarkdownRenderData();
        code.setMarkdown(markdownContent);
        MarkdownStyle style = MarkdownStyle.newStyle();
        style.setShowHeaderNumber(true);
        code.setStyle(style);
//      markdown样式处理与word模板中的标签{{md}}绑定
        Map<String, Object> data = new HashMap<>();
        data.put("md", code);

        Configure config = Configure.builder().bind("md", new MarkdownRenderPolicy()).build();
        try {
//            获取classpath
            String path = MarkdownUtil.class.getClassLoader().getResource("").getPath();
            log.info("classpath:{}", path);
            XWPFTemplate.compile(path + "markdown" + File.separator + "markdown_template.docx", config)
                    .render(data)
                    .writeToFile(path+"out_markdown_" + outputFileName + ".docx");
        } catch (IOException e) {
            log.error("保存为word出错");
        }

    }

    /**
     * 将markdown转换为word文档并下载
     *
     * @param markdownContent
     * @param response
     * @param fileName
     */
    public void convertAndDownloadWordDocument(String markdownContent, HttpServletResponse response, String fileName) {
        log.info("markdownContent:{}", markdownContent);
        MarkdownRenderData code = new MarkdownRenderData();
        code.setMarkdown(markdownContent);
        MarkdownStyle style = MarkdownStyle.newStyle();
        style.setShowHeaderNumber(true);
        code.setStyle(style);
//      markdown样式处理与word模板中的标签{{md}}绑定
        Map<String, Object> data = new HashMap<>();
        data.put("md", code);
        Configure config = Configure.builder().bind("md", new MarkdownRenderPolicy()).build();

        try {
            //获取classpath
            String path = MarkdownUtil.class.getClassLoader().getResource("").getPath();
            log.info("classpath:{}", path);
            XWPFTemplate template = XWPFTemplate.compile(path + "markdown" + File.separator + "markdown_template.docx", config)
                    .render(data);
            template.writeAndClose(response.getOutputStream());
            response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(fileName, "UTF-8") + ".docx");
            response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document;charset=utf-8");
        } catch (IOException e) {
            log.error("下载word文档失败:{}", e.getMessage());
        }
    }


    public static void main(String[] args) {
        String markdownContent = "# 一级标题\n" +
                "## 二级标题\n" +
                "### 三级标题\n" +
                "#### 四级标题\n" +
                "##### 五级标题\n" +
                "###### 六级标题\n" +
                "## 段落\n" +
                "这是一段普通的段落。\n" +
                "## 列表\n" +
                "### 无序列表\n" +
                "- 项目1\n" +
                "- 项目2\n" +
                "- 项目3\n" +
                "### 有序列表\n" +
                "1. 项目1\n" +
                "2. 项目2\n" +
                "3. 项目3\n" +
                "## 链接\n" +
                "[百度](https://www.baidu.com)\n" +
                "## 图片\n" +
                "![图片描述](https://www.baidu.com/img/bd_logo1.png)\n" +
                "## 表格\n" +
                "| 表头1 | 表头2 | 表头3 |\n" +
                "|-------|-------|-------|\n" +
                "| 单元格1 | 单元格2 | 单元格3 |\n" +
                "| 单元格4 | 单元格5 | 单元格6 |";
        toDoc(markdownContent, "test23");


    }
}

4.最终的输出效果

注意在生成有序列表或者无序列表时候不要加ref方法,因为加了ref方法虽然可以正常生成markdown文本,但是在转换成word内容的时候,commonmark会报错org.commonmark.node.OrderedList cannot be cast to org.commonmark.node.Paragra

还有一个注意事项,就是换行不要直接调用br()方法,因为转换的时候,在word文档中转换不了,直接生成了“<br>” 这样的文字,所以直接在markdown文本中使用"\n"来换行

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

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

相关文章

趋势外推法

趋势外推法主要利用图形识别法和差分法计算&#xff0c;进行模型的基本选择。 一、图形识别法。 这种方法是通过绘制散点图来进行的&#xff0c;即将时间序列的数据绘制成以时间 t 为横轴、时序观察值为纵轴的图形&#xff0c;观察并将其变化曲线与各类函数曲线模型的图形进行…

物联网系统中高精度温度检测方案_温度变送器

01 物联网系统中为什么要使用温度变送器 在物联网系统中使用温度变送器的原因主要可以归纳为以下几点&#xff1a; 1、温度监测与控制 实时数据获取&#xff1a;温度变送器能够将温度这一物理量转换为可传输的标准化电信号&#xff08;如4-20mA电流信号、0-10V电压信号或RS48…

Spring Boot 进阶-Spring Boot 开发第一个Web接口

在前面的文章中我们对Spring Boot的配置以及日志配置有了大概的了解,在我们搭建完成第一个Spring Boot项目之后也提到了一个概念就是RestFul风格的接口开发。下面我们就来详细介绍一下使用Spring Boot如何去开发一个RestFul的Web接口。 准备 在开发接口之前,需要引入的就是W…

一文上手Kafka【中】

一、发送消息细节 在发送消息的特别注意: 在版本 3.0 中&#xff0c;以前返回 ListenableFuture 的方法已更改为返回 CompletableFuture。为了便于迁移&#xff0c;2.9 版本添加了一个方法 usingCompletableFuture&#xff08;&#xff09;&#xff0c;该方法为 CompletableFu…

【工具类】证书自动续签免费版 正式发布

证书自动续签免费版 正式发布 1.控制台首页2.申请证书2.1.支持自动解析和手动解析两种模式2.2.LB&#xff08;负载均衡&#xff09;支持主流的云厂商2.3.CDN&#xff08;内容分发&#xff09; 支持主流的云厂商2.4.对象存储&#xff08;OSS&#xff09;支持主流云厂商2.5DCDN&a…

TDengine 在业务落地与架构改造中的应用实践!

前言 在物联网和大数据时代&#xff0c;时序数据的管理和分析变得至关重要。TDengine&#xff0c;作为一款专为时序数据设计的开源数据库&#xff0c;以其卓越的存储和查询效率&#xff0c;成为众多企业优化数据架构的优选。本文将分享我将TDengine成功应用于实际业务的经验&am…

elementui/plus灯下面的指示怎么改成圆圈或者隐藏

改成圆圈的步骤 改成没有indicator-position指示的位置/或者隐藏

用友U8 CRM 多个接口存在SQL注入漏洞

本文所涉及的任何技术、信息或工具&#xff0c;仅供学习和参考之用&#xff0c;请勿将文章内的相关技术用于非法目的&#xff0c;如有相关非法行为与文章作者无关。请遵守《中华人民共和国网络安全法》。 中华人民共和国网络安全法 第二十七条 规定 任何个人和组织不得从事非…

保姆级复现yolov7(论文复现)

保姆级复现yolov7&#xff08;论文复现&#xff09; 本文所涉及所有资源均在传知代码平台可获取 所需环境 当然深度学习环境的搭建是基础&#xff0c;详情可见(win10)&#xff1a; 【深度学习】windows10环境配置详细教程anaconda3环境变量配置win10-CSDN博客 Unbutun搭建深度学…

正则表达式的使用示例--Everything文件检索批量重命名工具

一、引言 Everything是一款非常实用的文件搜索工具&#xff0c;它可以帮助您快速定位并查找计算机中的文件和文件夹。Everything搜索文件资料之神速&#xff0c;有使用过的朋友们都深有体会&#xff0c;相对于Windows自带的搜索功能&#xff0c;使用Everything&#xff0c;可以…

C语言 | Leetcode C语言题解之第442题数组中重复的数据

题目&#xff1a; 题解&#xff1a; int* findDuplicates(int* nums, int numsSize, int* returnSize) { int *ans (int *)malloc(sizeof(int) * numsSize);int pos 0;for (int i 0; i < numsSize; i) {int x abs(nums[i]);if (nums[x - 1] > 0) {nums[x - 1] -…

$attrs 和 $listeners

通常情况下&#xff0c;父子组件之间的数据是通过 props 由父向子传递的&#xff0c;当子组件想要修改数据时&#xff0c;则需要通过 $emit 以事件形式交由父组件完成&#xff0c;而这种交互方式只存在于父子组件之间&#xff0c;多层嵌套的时候&#xff0c;处于内层的组件想要…

OpenAI o1与GPT-4o究竟强在哪里

OpenAI 的 O1 模型与 GPT-4o 相比&#xff0c;具有显著的技术进步和性能提升。以下是两者的主要区别和 O1 的进步之处&#xff1a; 推理能力&#xff1a;O1 模型在处理复杂问题&#xff08;如编程和数学&#xff09;方面表现出更强的推理能力。例如&#xff0c;在国际数学奥林匹…

使用ffmpeg合并视频和音频

使用ffmpeg合并视频和音频 - 哔哩哔哩 简介 FFmpeg是一套可以用来记录、转换数字音频、视频&#xff0c;并能将其转化为流的开源计算机程序。采用LGPL或GPL许可证。它提供了录制、转换以及流化音视频的完整解决方案。它包含了非常先进的音频/视频编解码库libavcodec&#xff0…

【项目文档】软件系统培训方案(Doc原件2024)

1. 培训概述 2. 培训目的 3. 培训对象及要求 3.1. 培训对象 3.2. 培训人员基本要求 4. 培训方式 5. 培训内容 6. 培训讲师 7. 培训教材 8. 培训质量保证 8.1. 用户培训确认报告 8.2. 培训疑问解答 软件全套资料部分文档清单&#xff1a; 工作安排任务书&#xff0c;可行性分析报…

【JAVA开源】基于Vue和SpringBoot的师生健康管理系统

博主说明&#xff1a;本文项目编号 T 052 &#xff0c;文末自助获取源码 \color{red}{T052&#xff0c;文末自助获取源码} T052&#xff0c;文末自助获取源码 目录 一、系统介绍二、演示录屏三、启动教程四、功能截图五、文案资料5.1 选题背景5.2 国内外研究现状5.3 可行性分析…

【数据结构】排序算法系列——计数排序(附源码+图解)

计数排序 顾名思义&#xff1a;统计每个数据出现的次数。 算法思想 我们根据《算法导论》中给出对于计数排序的讨论&#xff1a; 对每一个输入元素 x, 确定小于 x 的元素个数。利用这一信息&#xff0c;就可以直接把 x 放到它在输出数组中的位置上了。例如&#xff0c;如果…

秋招即将来临,AIGC 产品经理 快速入门方法论

AIGC 产品经理是什么 AIGC 产品经理是人工智能与大数据技术融合背景下应运而生的一种新型职业&#xff0c;负责从 AI 产品的设计、开发到推广的全过程&#xff0c;确保其顺利推向市场并实现良好的商业价值。 更具体地说&#xff0c;AIGC 产品经理就是将 AI 能生产内容的能力完…

【高景一号卫星】

高景一号卫星 高景一号卫星是中国自主研发的一系列高分辨率商业遥感卫星&#xff0c;旨在满足全球民用遥感影像市场的需求。以下是对高景一号卫星的详细介绍&#xff1a; 一、基本信息 名称&#xff1a;高景一号&#xff08;SuperView-1, SV-1&#xff09;发射时间&#xf…

数据库管理-第244期 一次无法switchover的故障处理(20240928)

数据库管理244期 2024-09-28 数据库管理-第244期 一次无法switchover的故障处理&#xff08;20240928&#xff09;1 问题展现2 问题排查与处理2.1 问题12.2 问题2 3 问题分析4 总结 数据库管理-第244期 一次无法switchover的故障处理&#xff08;20240928&#xff09; 作者&…