Markdown Table to XML Converter

Feed a table from a wiki page into a system that wants XML. Rows become elements, you name the tags, and a header like Order Total does not produce a file that refuses to parse.

To convert a Markdown table to XML, paste the table above. Each row becomes a row element inside a root element, both of which you name, and each column becomes a child element or an attribute. A header that is not a legal XML element name is rewritten and the original is kept in a name attribute, so the document parses and nothing is lost.

Need to rename the columns first? Open the app

Documents that have to become messages

The usual route to this page is an internal wiki. A team maintains a table of product codes, warehouse locations or tariff bands in a page everyone can edit, and some integration downstream consumes XML because it was written in 2009 and nobody is going to change it.

The second route is a specification. A partner sends a document, the table in it lists the fields or the codes, and you need that table as a file rather than as prose.

In both cases the value of the Markdown is that people maintain it, and the value of the XML is that a machine accepts it. This is the bridge, and the only thing that matters about the bridge is that the other end parses it first time.

Worked example

Take a release table from a runbook:

| service | version | released | replicas | notes |
|:--------|--------:|:--------:|---------:|:------|
| billing-api | 2.4.1 | 2026-01-08 | 6 | rollout paused |
| web-frontend | 5.0.0 | 2026-01-12 | 4 | canary 10% \| full 14 Jan |
| search-index | 1.19.3 | 2026-01-15 | 12 | |

With the root named releases and the row element named release:

<?xml version="1.0" encoding="UTF-8"?>
<releases>
  <release>
    <service>billing-api</service>
    <version>2.4.1</version>
    <released>2026-01-08</released>
    <replicas>6</replicas>
    <notes>rollout paused</notes>
  </release>
  <release>
    <service>search-index</service>
    <version>1.19.3</version>
    <released>2026-01-15</released>
    <replicas>12</replicas>
    <notes/>
  </release>
</releases>

The empty notes cell became <notes/>, an element that exists and has no content. Omitting it instead would make the document's shape change from row to row, which is the first thing a schema validator objects to.

Headers written for humans, names required by XML

Markdown headers are prose. People write Order Total, 2024 target, % change and Q1 (actual), because those read well in a rendered table. None of them is a legal XML element name.

An element name cannot contain a space, cannot start with a digit, and cannot begin with the letters x, m, l in that order. Write one anyway and you produce a file that no parser will open, with an error message that points at a byte offset and says nothing about which column caused it.

So names are corrected: anything illegal becomes an underscore, and a name that cannot start where it starts gains a leading one. Order Total becomes Order_Total, % change becomes __change, 2024 target becomes _2024_target. The original spelling is written on each element as name="Order Total", so the document is both parseable and lossless, and a note tells you how many columns were affected.

Elements or attributes, and the line-break caveat

Child elements are the default: readable, no length limit, and a value can contain anything once it is escaped.

Attribute mode gives you <row service="billing-api" version="2.4.1"/>, one self-closing element per row. It is considerably smaller and it is what a lot of older schemas expect.

The caveat is line breaks. A Markdown cell can hold one, written as <br>, and an attribute value cannot carry a newline in any way a reader will handle predictably. If your notes column has two-line cells in it, stay with elements.

What is deliberately not here

  • No schema. A DTD or XSD is specific to the specification you were sent, and a generated one would not match it.
  • No namespaces. Adding one you did not ask for is the fastest way to make a document fail validation somewhere else.
  • No nesting from dots. A header called limits.retries becomes an element spelled that way, not two nested elements. Guessing structure from punctuation breaks every header that legitimately has a dot in it.
  • No inline Markdown rendering. A cell holding **bold** produces those characters, escaped. This converts a table, not a document.

Frequently Asked Questions

My headers have spaces in them. Will the XML be valid?

Yes. A space is not allowed in an element name, so it becomes an underscore and the original spelling is written on the element as a name attribute. Order Total becomes Order_Total with name="Order Total", and a note says how many headers were affected.

What about a header that starts with a digit?

It gains a leading underscore, so 2024 target becomes _2024_target. XML reserves names beginning with a digit, and also anything starting with the letters x, m, l in that order, which gets the same treatment.

How is an empty cell represented?

As a self-closing element, so an empty notes cell becomes <notes/>. Leaving the element out would make the document's shape change from row to row, which is the first thing a schema validator complains about.

Can I put the values in attributes instead?

Yes, one switch. Attribute mode is much more compact and suits older schemas. The one thing it cannot do is carry a line break, so if your table has multi-line cells you want the element mode.

Does it emit a DTD or an XSD?

No. A schema belongs to the specification you were given, and one invented here would almost certainly disagree with it. What this guarantees is a well-formed document with the element names you asked for.

Is bold or a link inside a cell converted to markup?

No. Inline Markdown syntax comes through as escaped characters. This is a table converter, and in a real table the asterisks are usually part of the value rather than formatting.

Make the document the integration will accept

Well formed, legal names, originals preserved. Name the root and download.

Back to the converter