Skip to main content

XML to CSV Converter

Flatten XML into CSV so you can open it in Excel or feed it into other tools.

Import File

Maximum file size: 5.00 MB | Accepted format: .xml

Flattening XML is a sequence of decisions about what to discard: which element counts as a record, what happens to children that repeat, and where attributes go. A converter that guesses will produce a perfectly valid CSV that is missing data the document contained, and nothing downstream will flag it.

The element you call a record sets the shape of everything

A tree has no single correct rectangle. Choose a node too high and the whole file collapses into one row; too low and you get fragments of records. The choice that usually works is the shallowest element that repeats and carries the same children each time — which is a property you have to check, not assume.

Same-named elements at different depths are the first trap. If <item> appears both under <catalog> and inside each <order>, a selector that matches by tag name alone collects both sets and interleaves them. The DOM's getElementsByTagName searches all descendants and does exactly this; an absolute XPath such as /catalog/order/item does not. This tool takes every element named row anywhere in the document and, when it finds none, falls back to the direct children of the root — so records named something else that sit inside an extra wrapper will be read at the wrapper level.

Headers are usually derived from the first record alone. That is fine until record 400 carries a field the first one lacked: the column is never created and the value is dropped without a warning. If completeness matters more than speed, walk the whole document and take the union of child names before emitting a header row.

Repeated children have three answers and all of them lose something

A contact with zero to five <phone> children has no rectangular form. Keep the first and you silently discard the rest. Widen to phone_1 through phone_n, where n is the maximum across the file, and the header of every future export depends on the widest record in it — one new contact with three numbers changes the column count and quietly misaligns any consumer reading by position. Explode to one row per phone and the row count no longer equals the record count, so every aggregate over a parent column double-counts.

Two independent repeating groups compound the problem. Three phones and two addresses exploded together produce six rows, and not one of those six pairings exists in the source document. The workable compromise is to explode at most one group and serialise the other into a single cell with a delimiter that is not a comma, so the CSV quoting stays legible.

Whichever you pick, write it down next to the file. Most breakage from XML flattening is not the conversion failing — it is a downstream job that was built against a twelve-column export and receives thirteen.

Attributes, text nodes, and mixed content are three different things

<price currency="USD">9.99</price> carries two values, and the attribute is the one that disappears, because most flatteners read text content and never touch the attribute map. This tool is one of them: attributes are not exported. If the attribute matters, decide on a column name for it before converting — price and price@currency, say. There is no standard here; one tool writes @currency, another writes price_currency, and neither is wrong, which is why two flatteners give two different headers for the same document.

Mixed content — text interleaved with markup, as in <note>Ships <em>today</em> only</note> — has no rectangular representation at all. Reading text content concatenates every descendant and returns Ships today only, losing the emphasis; reading only the direct text nodes returns Ships  only. Neither string is the document, and you have to choose which lie to tell.

Pretty-printed input sharpens this. The whitespace between tags is a real text node, so the first child of an indented element is typically a newline plus spaces, and text content taken from a container returns the entire subtree with the indentation baked in. Read leaf elements rather than containers, and trim. Two other distinctions vanish permanently: <![CDATA[a<b]]> and a&lt;b both yield the characters a<b, so the CSV cannot tell you which the source used, and comments and processing instructions are dropped entirely along with any provenance they carried.

Namespaces are URIs; the prefixes are just labels

A prefix is arbitrary and local to the document. <ns1:item> and <x:item> are the same element type whenever both prefixes bind the same namespace URI, and two exports from the same vendor can differ in nothing but the prefix. Tools that key columns off the literal tag name — which includes the prefix — then produce different headers for equivalent documents, and a lookup by qualified name finds nothing the day the producer renames ns1 to ns2. Match on local name plus namespace URI instead, with getElementsByTagNameNS or local-name() in XPath.

There is one asymmetry worth memorising, because it produces an XPath that is obviously right and matches nothing. A default namespace declaration, xmlns="urn:example", applies to unprefixed elements but never to unprefixed attributes: an attribute with no prefix is in no namespace at all. So in <item xmlns="urn:example" id="1">, the element is in urn:example and id is not, and a query for the attribute in that namespace fails.

This is why SOAP envelopes, Office documents, and most vendor schemas cost half an hour the first time. The document declares a default namespace on the root, every unqualified path you write matches zero nodes, and the parser reports no error because zero matches is a legitimate result.

Order is lost, quoting is mandatory, and Excel will run your data

Sibling order is information in XML — the Infoset preserves it — and it is gone the moment someone sorts a column in a spreadsheet. If sequence carries meaning, such as line items on an invoice or steps in a workflow, emit an explicit index column during flattening. It cannot be reconstructed afterwards from anything in the CSV.

On the way out, RFC 4180 requires that any field containing a comma, a double quote, or a line break be wrapped in double quotes with embedded quotes doubled. XML text routinely contains all three. This converter quotes on exactly those characters and doubles the quotes, which is correct — but be aware that a newline inside a quoted field is legal CSV that still destroys any consumer naive enough to split on newlines before parsing.

Then there is the part that is a security issue rather than a formatting one. Excel, LibreOffice, and Google Sheets treat a cell starting with =, +, -, or @ as a formula, and XML content that began life as user input can start with any of them. Quoting the field does not help: a cell containing =HYPERLINK(...) is still evaluated on open. This is CWE-1236, formula injection, and the mitigations are to prefix suspect values with an apostrophe, or to ship an .xlsx where cell types are explicit. If you are going the other way and building XML from tabular data, the CSV to XML converter covers the escaping rules that apply in that direction.

ThenCatch is a free developer toolkit. Learn more about us.