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:
- Converting Excel workbooks to Markdown files
- Setting image paths and hyperlink formats when exporting Markdown
- Converting Markdown files to Excel workbooks
- Extracting multiple tables from Markdown documents and saving them into separate Excel worksheets
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.  |
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:
- Table1: Product sales data for East China
- Table2: Product sales data for South China
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.