XML to Markdown Table Converter

Show what is actually in the feed. Records become rows, attributes become columns alongside elements, and the result fits in an issue instead of an attachment.

To convert XML to a Markdown table, paste your document above. The repeating record element is detected automatically, each record becomes a table row with attributes and child elements as columns, nesting flattens with dot notation, pipes inside values are escaped, and numeric columns are right-aligned.

Want to keep only the interesting fields? Open the app

Nobody can read an XML feed in a ticket

The scenario is a support ticket or a bug report about an integration. The evidence is a feed, and pasting eighty lines of XML into the ticket means nobody will look at it. Six rows of a table means everybody will.

The second use is investigation. Before you write any code against a feed you have never seen, you want to know what fields it actually carries and what the values look like. A table answers that in one screen; the raw document takes ten minutes of scrolling.

Finding the records without asking you for a path

The hard part of reading XML as a table is deciding what a row is. A document has one root, and somewhere inside it is a repeating element that represents a record. Most tools make you supply an XPath expression for it.

That is a reasonable design and a bad first experience: you have to open the file, understand its structure, and write an expression before you see anything. Here the repeating element is found by looking for the deepest element name that occurs many times as a sibling, which is the record element in essentially every real feed.

Element children become columns, attributes become columns too, and nesting inside a record flattens with dot notation. What the detection picked is stated under the result, so if it guessed wrong on an unusual document you can see that immediately rather than wondering why the row count looks odd.

Worked example

A product feed:

<?xml version="1.0" encoding="UTF-8"?>
<products>
  <product id="SKU-101" category="Audio">
    <product_name>Wireless Mouse</product_name>
    <unit_price>23.07</unit_price>
    <in_stock>161</in_stock>
  </product>
  <product id="SKU-108" category="Accessories">
    <product_name>27in Monitor</product_name>
    <unit_price>140.33</unit_price>
    <in_stock>0</in_stock>
  </product>
</products>

And the Markdown:

| id | category | product_name | unit_price | in_stock |
| --- | --- | --- | ---: | ---: |
| SKU-101 | Audio | Wireless Mouse | 23.07 | 161 |
| SKU-108 | Accessories | 27in Monitor | 140.33 | 0 |

The two attributes are the first two columns and the child elements follow. Only unit_price and in_stock are right-aligned, because they are the only two columns whose every value is a number. id looks like it has structure and it has a prefix, so it is an identifier and stays left.

Attributes are data too

XML has two places to put a value and different specifications choose differently. <product id="SKU-101"> and <product><id>SKU-101</id></product> carry the same fact, and plenty of documents use both at once.

So attributes become columns alongside element children rather than being ignored. A converter that reads only child elements loses the identifier on the majority of real feeds, because the identifier is very often an attribute.

Where an attribute and a child element share a name, both survive as separate columns rather than one overwriting the other. It is rare, and when it happens losing one silently would be much worse than an extra column.

Escaping, and the width problem feeds always have

A pipe inside a value ends the cell. Feeds carry pipes in description fields and in anything that was once a delimited string, so every one is escaped as \|, with backslashes escaped first.

Entities in the source are resolved by the parser, so a description written as R&amp;D arrives as R&D and appears that way in the table, which is what the document means.

Width is the real difficulty. Feeds are wide: forty elements per record is normal for anything in logistics or finance, and a forty-column Markdown table is unreadable on any screen. Cutting it to the six fields the conversation is about is nearly always the right move, and the editor link above the fold is the fastest route.

What does not come across

  • Namespaces are stripped from column names, since a header of ns:price adds nothing once the document is gone.
  • Comments and processing instructions are dropped. Markdown has no place for them and a table is not a document reproduction.
  • The schema is not represented. A table shows what the values are, not what the specification says they may be.
  • Repeated child elements inside one record are kept as text in a single cell rather than being spread into extra rows, so the row count always equals the record count.

Frequently Asked Questions

Do I have to tell it which element is a record?

No. It is found from the document's own structure, by looking for the element name that repeats as a sibling, and the result says which one it picked. That means you see a table before you have had to understand the document.

Are attributes shown as columns?

Yes, alongside the child elements. Many specifications put the identifier in an attribute, so a converter that reads only child elements produces a table with no way to tell the records apart.

Are pipes in the values escaped?

Every one, with backslashes escaped first so a value ending in a backslash cannot eat the escape that follows. Feeds carry pipes in description fields more often than people expect.

What happens to entities in the source?

They are resolved by the parser, so a description written with an ampersand entity arrives as an ampersand and appears that way in the table. That is what the document means, and showing the entity text would misrepresent it.

My feed has forty elements per record. Will the table be usable?

It will convert and it will be too wide to read. Cutting to the fields the conversation is about is the right move for anything past eight columns; the editor link above the fold opens the same data where you can do it.

What about repeated child elements inside one record?

They are kept as text in a single cell rather than being spread into extra rows. Spreading them would make the table have more rows than the feed has records, which would be misleading in exactly the situation this is usually used for.

Make the feed readable

Records found for you, attributes kept, pipes escaped. One paste into the ticket.

Back to the converter