XML to Table

Drop an XML file or paste a snippet and it renders as a table on this page. Copy the whole thing into Excel, Sheets, or Docs in one click, or take the CSV. The parsing runs in your browser.

Need to filter or reshape the rows afterwards? Open the app

When you want a table, not a download

Most days I open an XML file I am not trying to produce a converted copy of it. I am trying to read it. A tree view answers questions about one record at a time; a table answers questions about all of them at once. These are the cases where the table is the whole point:

  • Build and config files. A pom.xml with 60 dependencies, an Android strings.xml, a .csproj full of item groups. Scrolling the tree is slow. Sixty rows with three columns is not.
  • SOAP and other legacy API responses. Banking, logistics, insurance and telecom endpoints still answer in XML. Laying 40 responses side by side is how you find out which fields are actually populated and which are always empty.
  • Exports from systems that only export XML. Payroll, ERP, older CRMs. Before importing anything you want to know the record count and the shape.
  • Sitemaps. A sitemap.xml with 3,000 URLs turns into loc, lastmod and priority columns. Paste it into a sheet, sort by lastmod, and the stale pages sort themselves to the top.
  • RSS and Atom feeds. One row per item with title, link and pubDate, which is exactly the shape a content tracker wants.

So on this page the table is the deliverable. Copy is the first button and the CSV download sits next to it, rather than the other way round.

Worked example: three inventory records

Here is a fragment of a stock export. Three repeating elements, two attributes on each, a currency attribute sitting on the price, and a nested block:

<inventory>
  <item sku="TP-118" status="active">
    <name>Thermal paper roll</name>
    <price currency="USD">2.40</price>
    <stock>
      <onHand>18</onHand>
      <warehouse>BLR-2</warehouse>
    </stock>
  </item>
  <item sku="LB-204" status="active">
    <name>Label sheets, A4</name>
    <price currency="USD">11.00</price>
    <stock>
      <onHand>140</onHand>
      <warehouse>BLR-2</warehouse>
    </stock>
  </item>
  <item sku="RB-009" status="discontinued">
    <name>Ribbon cartridge</name>
    <price currency="EUR">9.50</price>
    <stock>
      <onHand>0</onHand>
      <warehouse>AMS-1</warehouse>
    </stock>
  </item>
</inventory>

Paste that into the box above and this comes back:

sku status name price.currency price stock.onHand stock.warehouse
TP-118 active Thermal paper roll USD 2.40 18 BLR-2
LB-204 active Label sheets, A4 USD 11.00 140 BLR-2
RB-009 discontinued Ribbon cartridge EUR 9.50 0 AMS-1

Seven columns out of three levels of markup. The item element repeats, so it became the row. sku and status are attributes on that record element, so they became plain columns. <price currency="USD">2.40</price> carries an attribute and a text value at the same time, which is why it split into price.currency and price. The stock block contributed the last two.

Hit Copy table and two versions land on the clipboard together: tab separated text, which Excel and Google Sheets split into individual cells, and an HTML table, which Docs and Word paste as a real grid. Click A1, paste, and seven columns arrive in seven columns.

How the rows get picked

XML has no concept of a row, so something has to decide. The rule here is short enough to keep in your head: the parser looks at the direct children of the root element, groups them by tag name, and the largest group becomes the rows.

  • In the example above, the root had three item children and nothing else, so item won without a contest.
  • When a root mixes one metadata block with 500 record elements, record wins on count and the odd sibling is left out of the table.
  • Records buried further down are not found, because only the root's own children are counted. If your file looks like response, then body, then items, then item, copy the inner fragment into the paste box and the items become rows.

Columns come from what is inside each record:

  • An attribute on the record element keeps its own name. You get sku, not @sku.
  • A child element becomes a column named after its tag, and nesting joins the names with dots: stock.warehouse.
  • Dotted names run three levels below the record. Deeper than that, the branch is collapsed into its own text content and a note tells you how many places that happened in.
  • The column list is the union of every record's fields, in the order they were first seen. A field that only appears in row 40 still gets a column, and the rows above it are simply blank there.

Where XML fights back

  • Namespace prefixes are dropped. A feed of g:entry elements with g:id attributes gives you columns called id and title. Readable, and right almost always. The exception: two elements sharing a local name across different namespaces collapse into one column, and the later one wins.
  • Mixed content loses the loose text. Given <p id="1">lead <b>bold</b> tail</p> you get a b column holding bold. The words lead and tail are gone, because an element that has child elements is read through those children. Document-style XML is not really table material.
  • One record is not a table. A file with a single repeating element still renders, with a note saying the structure may not be tabular. Usually it means the real records sit a level deeper.
  • Long documents draw the first 500 rows. That keeps the page responsive. Copy and the CSV download both carry every row, and the count above the buttons is the true total.
  • Files stop at 100 MB. Past that the widget points you at the full editor, which streams rather than holding the entire document in memory.
  • Empty elements leave blank cells. A tag with no text contributes nothing for that record, so you get an empty cell instead of the word null.

Paste or file, both work

The paste tab takes a fragment straight out of a terminal or an editor, which is what you usually have when you are picking apart an API response. The file tab takes .xml, and .txt when that is how the export arrived. Names survive the trip: catalog.xml downloads as catalog.csv, with no export suffix bolted onto the end. Nothing is stored between visits either, so a reload gives you an empty box rather than yesterday's file.

Frequently Asked Questions

Does my XML get uploaded anywhere?

No. The document is read by your browser's own XML parser, inside the tab, and no request carries the file. Once the page has loaded you can pull the network cable and it still works.

How many rows does the table show?

The first 500, so a large document does not freeze the page while it draws. Copy and the CSV download both carry every row, and the row count printed above the buttons is the real total, not the number on screen.

Will the table paste into Excel with the columns intact?

Yes. Copy puts two versions on the clipboard at once: tab separated text, which Excel and Google Sheets split into individual cells, and an HTML table, which Google Docs and Word paste as a real grid. Click a cell and paste.

What happens to XML attributes?

They become ordinary columns. An attribute on the record element keeps its plain name, so sku stays sku with no @ prefix. An attribute on a nested element is dotted, so currency on a price element becomes price.currency.

Are namespace prefixes preserved?

No, they are stripped. A g:title element becomes a column called title. That keeps column names readable, with one catch: two elements that share a local name but come from different namespaces end up in the same column.

How big a file can it handle?

The in-page parser stops at 100 MB. Past that it hands you over to the full editor, which streams the document instead of holding all of it in memory at once.

See your XML as a table

Paste a fragment or drop the file. The table renders here, Copy fills a spreadsheet, and the CSV is one click away.

Back to the converter