跪拜 Guibai
← Back to the summary

Java Library Turns Excel Tables Into Markdown and Back

Title: Implementing Excel and Markdown Interconversion in Java: Table Export, Format Settings, and Multi-Table Extraction

In development, we often need to convert table data between Excel and Markdown. For example, export statistical results from Excel to Markdown for README, technical documentation, or project reports; or import tables from Markdown documents into Excel for further editing, filtering, and analysis. For files that need batch processing or regular updates, automating conversion in Java can reduce manual effort and improve processing efficiency.

This article describes how to use Java to implement bidirectional conversion between Excel and Markdown, mainly covering:

1. Preparing the Development Environment

This article uses Spire.XLS for Java to process Excel files. The library supports reading, editing, and converting Excel files independently, without requiring Microsoft Office.

Excel-to-Markdown conversion support has been available since version 15.11.3; this article uses version 16.10.1.

Add the following configuration to the pom.xml in the Maven project:

<repositories>
    <repository>
        <id>com.e-iceblue</id>
        <name>e-iceblue</name>
        <url>https://repo.e-iceblue.com/nexus/content/groups/public/</url>
    </repository>
</repositories>

<dependencies>
    <dependency>
        <groupId>e-iceblue</groupId>
        <artifactId>spire.xls</artifactId>
        <version>16.10.1</version>
    </dependency>
</dependencies>

2. Converting Excel to Markdown with Java

Spire.XLS for Java supports exporting an Excel workbook directly to a Markdown file. After loading the Excel file with Workbook.loadFromFile(), call the saveToFile() method and specify the output format as FileFormat.Markdown to complete the conversion.

Implementation code:

import com.spire.xls.Workbook;
import com.spire.xls.FileFormat;

public class ExcelToMarkdown {
    public static void main(String[] args) {

        Workbook workbook = new Workbook();

        try {
            // Load the Excel file
            workbook.loadFromFile("report.xlsx");

            // Export to Markdown
            workbook.saveToFile("report.md", FileFormat.Markdown);

        } finally {
            workbook.dispose();
        }
    }
}

After running, a report.md file is generated. Assuming the original Excel contains a table of department work completion, the converted Markdown content looks like this:

| 部门 | 负责人 | 完成率 |
| --- | --- | --- |
| 技术部 | 张明 | 92% |
| 市场部 | 李华 | 88% |
| 客服部 | 王芳 | 95% |

Note that Markdown does not support Excel formulas, merged cells, or complex styles. If the original workbook contains these features, check whether the exported data and table structure match expectations.

3. Customizing Image and Hyperlink Formats in Excel-to-Markdown Export

If the Excel file contains images or hyperlinks, in addition to the table data itself, you need to consider how these items are stored in Markdown.

For example, when uploading the generated Markdown file to GitHub, you often want images to use relative paths so the whole documentation directory can be moved together. For hyperlinks, you can choose inline links or reference-style links.

Spire.XLS for Java provides the MarkdownOptions class to control these export options.

Two useful methods are:

Method Effect
isSavePicInRelativePath(true) Saves image references using relative paths, e.g. ![image](images/pic.png)
isSaveHyperlinkAsRef(true) Saves hyperlinks as reference-style Markdown links

For example, an inline link in Markdown is usually:

[Project website](https://www.example.com)

After setting isSaveHyperlinkAsRef(true), a reference-style format similar to the following can be used:

[Project website][ref1]

[ref1]: https://www.example.com

The following code demonstrates how to make images use relative paths while saving hyperlinks as inline format:

import com.spire.xls.Workbook;
import com.spire.xls.MarkdownOptions;

public class ExcelToMarkdownWithOptions {
    public static void main(String[] args) {

        Workbook workbook = new Workbook();

        try {
            workbook.loadFromFile("report.xlsx");

            // Configure Markdown export options
            MarkdownOptions options = new MarkdownOptions();
            options.isSavePicInRelativePath(true);
            options.isSaveHyperlinkAsRef(false);

            // Export Markdown using the specified options
            workbook.saveToMarkdown("report_custom.md", options);

        } finally {
            workbook.dispose();
        }
    }
}

Unlike the previous section, this uses the saveToMarkdown() method and passes a MarkdownOptions object.

To use reference-style links, just change isSaveHyperlinkAsRef(false) to true.

For files to be published to GitHub or static documentation sites, relative image paths are usually more convenient to manage. However, keep the exported image resources and ensure the relative directory structure between the images and the Markdown file remains unchanged when publishing.

4. Converting Markdown to Excel with Java

Besides exporting Excel to Markdown, sometimes you need to re-import tables from Markdown documents into Excel.

The flow for converting Markdown to Excel is also simple: use the Workbook.loadFromMarkdown() method to load the Markdown file directly, then save it as Excel with saveToFile().

Implementation code:

import com.spire.xls.Workbook;
import com.spire.xls.ExcelVersion;

public class MarkdownToExcel {
    public static void main(String[] args) {

        Workbook workbook = new Workbook();

        try {
            // Load the Markdown file
            workbook.loadFromMarkdown("tasks.md");

            // Save as an Excel workbook
            workbook.saveToFile(
                    "tasks.xlsx",
                    ExcelVersion.Version2016
            );

        } finally {
            workbook.dispose();
        }
    }
}

Assume tasks.md contains the following task table:

| Task | Owner | Status |
| --- | --- | --- |
| API development | Zhang Ming | Completed |
| Page optimization | Li Hua | In progress |
| Feature testing | Wang Fang | Pending |

After running the program, a tasks.xlsx file is generated, which you can view in Excel and edit or process further.

Note that Markdown itself does not contain Excel cell type information. After conversion, if you need to perform calculations on numbers, dates, and other data, check whether cells are correctly recognized as numbers or dates rather than plain text.

5. Extracting Multiple Tables from Markdown and Saving Them to Different Excel Worksheets

Using loadFromMarkdown() directly is suitable for converting a whole file. However, in some projects, you may only want the tables in a Markdown document, not the other explanatory text.

For example, a sales report records multiple tables by region:

# Regional Sales Report

## East China

| Product | Sales | Revenue |
| --- | ---: | ---: |
| Product A | 120 | 18000 |
| Product B | 85 | 12750 |

## South China

| Product | Sales | Revenue |
| --- | ---: | ---: |
| Product A | 96 | 14400 |
| Product B | 110 | 16500 |

If you want to write the East China and South China data into separate Excel worksheets, you can first parse the Markdown tables, then create worksheets and write the data using Spire.XLS for Java.

The example below uses the Java standard library to read the Markdown file, identify the header and separator rows, and save each table to its own worksheet.

Java Implementation Code

import com.spire.xls.Workbook;
import com.spire.xls.Worksheet;
import com.spire.xls.ExcelVersion;

import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;

public class MarkdownTablesToExcel {

    public static void main(String[] args) throws Exception {

        // Read the Markdown file
        List<String> lines = Files.readAllLines(
                Paths.get("report.md"), StandardCharsets.UTF_8);

        Workbook workbook = new Workbook();
        int tableCount = 0;
        boolean inCodeBlock = false;
        char fenceChar = '\0';
        int fenceLength = 0;

        try {
            workbook.getWorksheets().clear();

            for (int i = 0; i < lines.size() - 1; i++) {
                String line = lines.get(i).trim();

                // Skip Markdown code blocks
                if (inCodeBlock) {
                    int length = countLeading(line, fenceChar);
                    if (length >= fenceLength
                            && line.substring(length).trim().isEmpty()) {
                        inCodeBlock = false;
                    }
                    continue;
                }

                int backticks = countLeading(line, '`');
                int tildes = countLeading(line, '~');

                if (backticks >= 3 || tildes >= 3) {
                    fenceChar = backticks >= 3 ? '`' : '~';
                    fenceLength = Math.max(backticks, tildes);
                    inCodeBlock = true;
                    continue;
                }

                if (!isTableRow(line)) {
                    continue;
                }

                List<String> headers = splitCells(line);
                List<String> separators =
                        splitCells(lines.get(i + 1).trim());

                // Check header and separator row
                if (headers.isEmpty()
                        || headers.size() != separators.size()
                        || !isSeparator(separators)) {
                    continue;
                }

                // Create a worksheet for each table
                Worksheet sheet = workbook.getWorksheets()
                        .add("Table" + (++tableCount));

                // Write headers and set bold
                for (int col = 0; col < headers.size(); col++) {
                    sheet.getCellRange(1, col + 1)
                            .setText(headers.get(col));

                    sheet.getCellRange(1, col + 1)
                            .getStyle().getFont().isBold(true);
                }

                // Write table data
                int row = 2;
                int j = i + 2;

                while (j < lines.size()
                        && isTableRow(lines.get(j).trim())) {

                    List<String> cells =
                            splitCells(lines.get(j).trim());

                    if (cells.size() != headers.size()) {
                        break;
                    }

                    for (int col = 0; col < cells.size(); col++) {
                        sheet.getCellRange(row, col + 1)
                                .setText(cells.get(col));
                    }

                    row++;
                    j++;
                }

                // Auto-fit column widths
                sheet.getAllocatedRange().autoFitColumns();

                i = j - 1;
            }

            if (tableCount == 0) {
                System.out.println("No Markdown tables found.");
                return;
            }

            // Save the Excel workbook
            workbook.saveToFile(
                    "tables.xlsx", ExcelVersion.Version2016);

            System.out.println("Extracted " + tableCount + " tables.");

        } finally {
            workbook.dispose();
        }
    }

    // Count the number of consecutive leading characters in a line
    private static int countLeading(String line, char marker) {
        int i = 0;
        while (i < line.length() && line.charAt(i) == marker) {
            i++;
        }
        return i;
    }

    // Determine whether a line is a table row with leading and trailing pipes
    private static boolean isTableRow(String line) {
        return line.startsWith("|") && line.endsWith("|");
    }

    // Validate a Markdown table separator row
    private static boolean isSeparator(List<String> cells) {
        if (cells.isEmpty()) {
            return false;
        }

        for (String cell : cells) {
            if (!cell.matches(":?-+:?")) {
                return false;
            }
        }
        return true;
    }

    // Split table cells
    private static List<String> splitCells(String line) {

        List<String> cells = new ArrayList<>();

        if (!isTableRow(line)) {
            return cells;
        }

        String content = line.substring(1, line.length() - 1);
        StringBuilder cell = new StringBuilder();

        for (int i = 0; i < content.length(); i++) {
            char ch = content.charAt(i);

            // Handle escaped pipe |
            if (ch == '\\' && i + 1 < content.length()
                    && content.charAt(i + 1) == '|') {
                cell.append('|');
                i++;
            } else if (ch == '|') {
                cells.add(cell.toString().trim());
                cell.setLength(0);
            } else {
                cell.append(ch);
            }
        }

        cells.add(cell.toString().trim());
        return cells;
    }
}

Implementation Notes

This code first skips Markdown code blocks, then identifies tables based on header and separator rows. For example:

| --- | ---: | :---: |

After identifying a table, the program splits each column with splitCells(), creates a worksheet with getWorksheets().add(), and writes data with setText(). Finally, autoFitColumns() is called to adjust column widths automatically.

For the regional sales report above, the generated tables.xlsx will contain two worksheets:

Applicability: This example is suitable for structurally regular Markdown tables with leading and trailing pipes. It can recognize common separator rows, simple escaped pipes, and skip ordinary code blocks delimited by backticks or tildes. For complex escapes, pipes inside inline code, or nested structures, a dedicated Markdown parser library is recommended.

In addition, the example uses setText() to write data, so cell contents are treated as text. If you need to perform calculations on fields such as sales volume or revenue, use setNumberValue() to write numeric values according to the data type.

6. Issues to Note During Conversion

1. Can formulas and cell formatting be preserved after Excel-to-Markdown conversion?

Markdown does not support Excel formula calculation, conditional formatting, or complex cell styles, so these features cannot be fully preserved after conversion.

For workbooks containing formulas, confirm that the formula results have been updated before conversion; for tables with complex formatting, check whether the exported Markdown matches expectations.

2. Why does the converted Markdown table display incorrectly?

Usually check the table structure and the support of the target Markdown renderer.

For example, merged cells, multi-level headers, or complex layouts in Excel cannot be directly mapped to standard Markdown tables. For such content, arrange the data into a regular data region in Excel first, then perform the conversion.

3. Why might numbers not be calculable after Markdown-to-Excel conversion?

If numbers are written as text, it may affect summation, statistics, or other numeric calculations in Excel.

For example, when writing cells with Spire.XLS for Java, choose different methods based on the data type:

// Write text
sheet.getCellRange("A1").setText("1200");

// Write a number
sheet.getCellRange("A2").setNumberValue(1200);

If you later need to calculate sales, averages, or generate charts, write text, numbers, and dates separately based on the field type when importing data.

Summary

Implementing bidirectional conversion between Excel and Markdown in Java projects reduces the manual work of copying tables and reorganizing data.

This article introduced how to use Spire.XLS for Java to perform bidirectional conversion between Excel and Markdown, and how to customize the export format for image paths and hyperlinks. It also demonstrated how to extract multiple tables from Markdown documents and save them to separate Excel worksheets. In actual development, you can choose to convert the entire file directly, or extract specified table data for processing.