CSV to XML Converter

A CSV to XML converter turns rows into elements. This one lets you name the root and the row element, write values as children or as attributes, and it fixes the column headings that XML will not accept as element names while keeping the original in a name attribute, so nothing is lost and the document actually parses. Nothing is uploaded.

Need to rename headings properly first? Open the app

Your headings are probably not legal XML names

An XML element name has rules, and spreadsheet headings routinely break all of them at once. A name cannot start with a digit. It cannot contain a space, a slash, a parenthesis, a percent sign or most punctuation. It cannot start with a hyphen or a dot. And it cannot begin with the letters xml in any combination of cases, because the specification reserves those for itself.

A heading of 2024 Total (USD) breaks four of those. Writing it straight through gives you:

<2024 Total (USD)>41250</2024 Total (USD)>

which is not XML. No parser will open the file, and the error you get points at the character position rather than telling you a column name is the problem.

The sanitising here is straightforward: anything outside letters, digits, underscore, hyphen and dot becomes an underscore, and a leading digit, dot, hyphen or xml gets an underscore in front. What makes it non-destructive is the second half. Every element whose name changed carries the original heading in a name attribute:

<_2024_Total__USD_ name="2024 Total (USD)">41250</_2024_Total__USD_>

So a round trip back to a table can recover the heading you started with, and a person reading the file can see what the column was actually called. The count and the first several renames are listed in the warnings.

Two headings that sanitise to the same name are kept apart with a numeric suffix. total-usd and total usd both want to become total_usd, and letting them would silently merge two columns into one.

A worked example

Two products, one supplier name with an ampersand, one blank cell:

sku,name,price,discount,supplier
00412,Bracket 40mm,4.25,10%,Nordwerk GmbH
01730,Bronze bushing,2.15,,Fenwick & Sons

With the root named products and the row named product:

<?xml version="1.0" encoding="UTF-8"?>
<products>
  <product>
    <sku>00412</sku>
    <name>Bracket 40mm</name>
    <price>4.25</price>
    <discount>10%</discount>
    <supplier>Nordwerk GmbH</supplier>
  </product>
  <product>
    <sku>01730</sku>
    <name>Bronze bushing</name>
    <price>2.15</price>
    <discount/>
    <supplier>Fenwick &amp; Sons</supplier>
  </product>
</products>

The ampersand is an entity. The blank discount is a self-closing empty element rather than being omitted, so every row has the same shape and an XSD with a required element is satisfied. And the SKU is still 00412: everything is written as text, so a leading zero has nothing to lose it to.

Switch to attributes and the same rows compress to two lines. Empty values are simply absent, because an attribute with an empty string and an absent attribute are different claims and the absent one is the honest reading of a blank cell:

  <product sku="00412" name="Bracket 40mm" price="4.25" discount="10%" supplier="Nordwerk GmbH"/>
  <product sku="01730" name="Bronze bushing" price="2.15" supplier="Fenwick &amp; Sons"/>

Attributes, newlines and CDATA

Attributes are compact, and they have one hard limit: an attribute value cannot carry a line break or a tab. XML normalises whitespace in attribute values, so a newline written into one comes back as a space and the shape of the cell is gone.

Rather than silently flattening those values or refusing the whole file, that one value is written as a child element instead, inside the row that would otherwise have been self-closing. The row keeps its other attributes, the multi-line value keeps its newlines, and the count of values that needed it is reported. It is a mixed document, which is slightly less tidy and entirely correct.

The CDATA option exists for consumers that expect free text wrapped rather than escaped. The detail worth knowing is what happens when a value itself contains ]]>, the three characters that end a CDATA section. You cannot escape them inside one, so the section is closed and immediately reopened around them. That is the standard trick and the only thing that works; a converter that does not do it produces a file that ends its section early and then reads your data as markup.

The indentation control goes down to a single line with no whitespace at all, which is what you want when the file is going over a wire rather than in front of a person. The XML declaration can be switched off for a fragment being embedded in a larger document.

One thing this deliberately does not do is guess at nesting. A heading of address.city becomes an element called address.city, not an address element with a city inside it. Building structure from punctuation is guesswork, and it mangles any column whose name legitimately contains a dot.

Questions

My column is called 2024 Total (USD). What happens to it?

It becomes the element _2024_Total__USD_ and the original heading is written into a name attribute on every one of those elements, so nothing is lost. XML names cannot begin with a digit and cannot contain spaces or parentheses, so a converter that writes the heading straight through produces a file no parser will open. The count of adjusted names is reported rather than left for you to notice.

Why does a column called xmlns get an underscore?

Because the XML specification reserves every name beginning with the letters x, m, l in any combination of cases for its own use. Names like xmlns and xml:lang have defined meanings, and inventing your own xml-prefixed element is not allowed. Prefixing an underscore keeps the name legal and keeps it recognisable.

Should I use elements or attributes for the values?

Both are correct XML and the choice depends on the consumer. Attributes are compact and produce a much smaller file, and they cannot hold a line break. Elements can hold anything, are easier to extend later with nested children, and are what most XSD schemas and XSLT stylesheets expect. If you pick attributes and a value holds a newline or a tab, that one value is written as a child element instead and the count is reported, because the alternative would be a corrupt file.

What about characters like ampersands in the data?

Escaped, always. An ampersand, a less-than, a greater-than and a double quote all become entities in element text and in attribute values, and an apostrophe is escaped too so single-quoted attributes are safe. A supplier called Fenwick and Sons written literally would make the document malformed, and a malformed XML file does not open at all rather than opening slightly wrong.

What is the CDATA option for?

It wraps values in a CDATA section instead of escaping them, which some legacy consumers expect for free-text fields. The detail that matters is that a value containing the three characters that end a CDATA section cannot simply be placed inside one; the section is split and reopened around them, which is the standard trick and the only correct one. Most of the time escaping is the better choice, and it is the default.

Can two columns end up as the same element?

No. Two different headings can sanitise to the same legal name, for example total-usd and total usd both becoming total_usd, which would merge two columns into one on the way back. The second is suffixed apart and the change is listed in the warnings alongside the other renames.

Is anything uploaded?

No. The conversion runs in your browser tab with nothing sent to a server, nothing stored between visits and no row cap. If you want the reverse direction, the XML to CSV converter on this site reads arbitrary XML and works out which repeated element is the row.

Convert your CSV to XML

No sign-up, no upload, no row cap. Your own element names, everything escaped, illegal headings fixed without losing them.

Back to the converter