XML to CSV: How to Flatten XML Files Into Spreadsheet-Ready Rows

Convert XML to CSV the right way: free online tools, Python ElementTree and pandas, Excel Power Query imports, and RSS feed handling. Step-by-step.

XML is the cockroach of data formats: declared dead a dozen times, still running half the world’s integrations. Product feeds, sitemap files, SOAP APIs, government open-data portals, RSS feeds, and countless enterprise exports all arrive as .xml — and sooner or later you need that data in a spreadsheet. Knowing how to turn xml to csv without losing the structure that matters is a practical superpower for analysts, marketers, and developers alike. The core challenge mirrors JSON’s: XML is hierarchical (elements inside elements, attributes on elements) while CSV is flat. This guide walks through four approaches — quick online conversion, Python with ElementTree and pandas, Excel’s built-in Power Query XML import, and special handling for RSS feeds — plus the mapping decisions that determine whether your output is actually useful.

A word on expectations: no converter understands your data’s meaning — it only sees structure. The tool can turn elements into columns, but deciding which elements deserve columns is your job, and it’s the most important part of the process. Get the mapping right and the rest is straightforward mechanics.

We’ll work with a running example: a small product catalog in XML, with nested categories and attributes. The same techniques scale to hundred-megabyte exports. If your data is already flat, our free CSV to Excel converter finishes the job; this guide covers the XML-specific flattening that comes first. For JSON’s version of the same problem, see our JSON to CSV guide.

Markup document with angle-bracket tags converting through a curved arrow into flat comma-separated table rows
XML’s hierarchy has to become a flat grid — the mapping decisions make or break the result.

Convert XML to CSV online: the quick route

For the online route, upload the file to a free converter and download the result. For simple, regular XML — a flat list of records with identical children — this genuinely works in seconds. The converter picks the repeating element as the row unit, turns child elements into columns, and hands you a clean CSV.

The failure modes are specific to XML. Attributes get dropped: many converters only read element text and silently ignore attributes like <product id="P1"> — if your IDs live in attributes, they’re gone. Namespaces confuse parsers: enterprise XML loves namespaces (<ns0:product>), and naive converters treat each namespaced tag as a different column. Deep nesting gets stringified: a nested address element becomes one cell of raw XML text. Before trusting an online result, verify: row count matches the repeating elements, ID-like attributes survived, and no cell contains raw angle brackets. For anything that fails those checks, the Python or Power Query routes below give you full control.

Plain text file icon with structured rows transforming through a curved arrow into a green spreadsheet icon
Simple, regular XML converts online in seconds — complex files need the methods below.

What makes XML tricky: elements, attributes, and nesting

Consider our product catalog:

<catalog>
  <product id="P1" category="audio">
    <name>Wireless Headphones</name>
    <price currency="USD">79.99</price>
    <specs>
      <battery>40h</battery>
      <bluetooth>5.3</bluetooth>
    </specs>
  </product>
  ...
</catalog>

Three structural features need decisions. Attributes (id, category, currency) carry real data but aren’t elements — the standard flattening promotes them to columns, conventionally named like id, category, price_currency, or with an @ prefix. Nesting (specs/battery) becomes dot- or underscore-joined column names: specs_battery. Repeating children (multiple <product> elements) define your rows — one row per product.

The XML specification also allows mixed content (text and elements interleaved), comments, processing instructions, and CDATA sections — all of which a converter must decide to keep or drop. Real-world files add namespaces, inconsistent casing, and ragged structures where some products have specs and others don’t. Understanding these features is what lets you evaluate a converter’s output intelligently instead of trusting it blindly. Our YAML to Excel guide covers similar flattening decisions for configuration files.

Nested branching data tree diagram flattening through a curved arrow into a single flat spreadsheet table
Attributes, nested elements, and repeating nodes each need an explicit flattening rule.

XML to CSV with Python: ElementTree and pandas

Python’s standard library includes xml.etree.ElementTree — no installs needed — and pairing it with pandas gives you a complete xml to csv python pipeline. The pattern: find the repeating element, extract each one into a flat dict, build a DataFrame:

import xml.etree.ElementTree as ET
import pandas as pd

tree = ET.parse("catalog.xml")
rows = []
for p in tree.getroot().findall("product"):
    rows.append({
        "id": p.get("id"),                       # attribute
        "category": p.get("category"),           # attribute
        "name": p.findtext("name"),
        "price": p.findtext("price"),
        "currency": p.find("price").get("currency"),
        "battery": p.findtext("specs/battery"),  # nested path
        "bluetooth": p.findtext("specs/bluetooth"),
    })

pd.DataFrame(rows).to_csv("catalog.csv", index=False)

Notice what’s happening: .get() reads attributes, .findtext() reads element text (returning None when absent — ragged structures handled gracefully), and slash paths like specs/battery reach into nesting. This explicit mapping is the method’s great strength: you decide exactly which elements become columns, with what names, in what order. For namespaces, register them (namespaces = {"ns": "http://..."}) and use .find("ns:product", namespaces) — the step most people miss when their findall returns nothing.

For very large files, ET.parse loads everything into memory. Switch to iterparse with element clearing, or use lxml‘s streaming parser — same logic, constant memory. And to turn any xml file to csv excel users can open without fuss, write with encoding="utf-8-sig" (the BOM helps Excel detect UTF-8) and keep column names simple: no spaces is fine, but avoid special characters that confuse downstream tools.

Code editor window with colorful data-processing code and a green play button, outputting a clean spreadsheet table below
ElementTree for parsing, pandas for writing — explicit mapping, total control.

Map XML elements to columns deliberately

The difference between a useful CSV and a confusing one is the mapping — and it’s worth doing on paper before you code. List every distinct element and attribute path in the file (a quick script can print the unique paths), then decide for each: column (keep as its own field), drop (metadata you don’t need, like internal IDs or processing timestamps), or combine (merge related fields, like first/last name, into one). Name columns for humans: price_usd beats price_currency_price, and consistent snake_case beats the mixed conventions converters invent.

Repeating groups at different levels need a grain decision: a catalog with categories containing products can become one row per product (category repeated) or one row per category (products joined). Choose the grain your analysis needs — product-level for pricing work, category-level for assortment overviews. You can always aggregate up, but you can’t disaggregate a joined cell.

Three mapping judgments come up constantly. IDs: keep both the XML attribute ID and any human SKU — analysts join on one, humans read the other. Dates: normalize to ISO 8601 (2026-10-08) during conversion; XML dates arrive in a dozen formats and Excel misreads half of them. Multi-valued elements (several <tag> children): join with a delimiter (|) into one cell unless tags are your unit of analysis — exploding them multiplies rows fast. Document your mapping in a comment at the top of the script; six months from now, “why is there no description column?” should have an answer.

Labeled data element nodes with connecting lines mapping into spreadsheet column headers above a data grid
Explicit element-to-column mapping: decide once, document it, automate it.
Mapping checklist: Before you code, list every element path, mark each as column / drop / combine, fix your naming convention, and note the row-grain decision. Thirty minutes with this list saves hours of rework — and it becomes the documentation your future self will thank you for.

No-code route: Excel Power Query imports XML directly

Excel’s Power Query reads XML natively: Data › Get Data › From File › From XML. The file loads as a table of its top-level elements; click into nested tables and use the expand arrows to flatten records and lists step by step — attributes appear as columns automatically. Like the JSON import, every click is recorded as a refreshable query, so a weekly product feed becomes a one-click refresh.

Power Query’s XML handling has genuine advantages: it discovers structure visually (you see the nesting before committing), handles namespaces without code, and its type detection converts numbers and dates sensibly. The limits: very deep nesting gets click-heavy, files over ~100 MB slow the editor, and the auto-generated column names (product.id, specs.battery) need renaming for human consumption. For business users processing supplier catalogs or marketplace feeds in the US, UK, or EU, this is usually the sweet spot — no Python environment, results straight in the workbook. Microsoft’s data import documentation covers the shared Get Data foundation.

Magic wand with sparkles cleaning a spreadsheet, turning messy red-flagged cells into clean green cells
Power Query records every expansion step — next week’s feed refreshes with one click.

RSS feeds, sitemaps, and exports: the common real-world cases

RSS/Atom feeds are the friendliest XML you’ll meet: a flat list of <item> elements with predictable children (title, link, pubDate, description). Content marketers tracking competitor blogs, researchers monitoring journals, and developers watching release notes all convert feeds the same way — parse, extract the four or five fields you care about, one row per item, done. Python’s feedparser library handles the RSS/Atom dialect differences for you; ElementTree works fine too since the structure is simple.

SOAP API responses deserve a special mention because they wrap your data in layers of envelope boilerplate. Don’t map the envelope — navigate straight to the payload element (usually a few levels down) and treat that as your document root. Everything above it is transport packaging. A quick way to find the payload: pretty-print the response and look for the first repeating element with business-meaningful children; that’s your row unit.

XML sitemaps (sitemap.xml) are even simpler: a list of URLs with last-modified dates. SEOs convert these to audit coverage — which pages exist, which are stale. Enterprise exports (SOAP responses, ERP dumps) are the hard case: deep nesting, namespaces, ragged structures. For those, invest in the explicit Python mapping rather than fighting a converter — the file’s complexity demands deliberate decisions. And Office files themselves (.xlsx, .docx) are zipped XML; tools like openpyxl read them directly, which is occasionally the cleanest “XML to spreadsheet” path of all. When the flattened data needs Excel formatting rather than raw CSV, our JSON to CSV guide’s finishing section shows the import-side best practices that apply equally here.

Configuration exports from network gear, CMS platforms, and dev tools are a sneaky-hard case: deeply nested, full of repeated boilerplate elements, and mixed with settings you don’t need. The winning move is aggressive dropping — map only the ten or twenty elements that matter for your analysis and ignore the rest. A 5 MB config file usually contains about 200 rows of actual interest buried in scaffolding.

Namespaces in one minute: If your XML has tags like <ns:product>, every search must include the namespace. Print the root tag first — the namespace URL hides inside it. This single gotcha accounts for most “my parser finds nothing” complaints.

Validate the output: the converter’s checklist

XML conversions fail quietly — the file looks plausible while missing data. Run this checklist on every result. Row count: count the repeating elements in the source (a quick grep -c "<product" or the parser’s element count) and confirm the CSV has that many data rows. Attribute survival: pick three records and verify their ID-like attributes appear as columns with correct values. No raw markup: search the CSV for < — any hit means nested content got stringified instead of flattened. Column sanity: dates look like dates, numbers sum correctly, and text isn’t truncated mid-value.

Ragged coverage deserves its own check: sort by a column that should always be present (like name) and scan for blanks that shouldn’t be there — they reveal elements the mapping missed. And round-trip a sample: take five source records, convert, and eyeball every field against the original XML. This takes ten minutes and catches the mapping errors — dropped attributes, mis-nested paths, wrong row grain — that silently corrupt downstream analysis. A conversion you haven’t validated is a rumor, not data.

From CSV to a polished Excel workbook

The CSV is an intermediate, not the destination. Import it with Data > From Text/CSV (never double-click — the import dialog lets you set UTF-8 encoding and column types explicitly), then do the finishing pass: rename machine-generated columns (specs_battery to Battery), set number and date formats, freeze the header row, and convert to a Table (Ctrl+T) for filtering. If the XML keeps arriving — a weekly supplier catalog, a daily feed — save the Power Query steps or the Python script so the whole chain replays.

For catalog-style data, consider one worksheet per category instead of one giant sheet: filter the table by category into separate sheets, or have the Python script write multiple sheets with pd.ExcelWriter. Shoppers, merchandisers, and marketplace sellers in the US, UK, and India all work faster with categorized sheets than with a 50,000-row monolith. And when the workbook is the deliverable rather than the CSV, our free CSV to Excel converter handles the final hop in seconds — or keep the .xlsx the script writes directly.

Troubleshooting: when the conversion goes wrong

findall returns nothing. Namespaces. Inspect the root tag — if it contains a URL in braces, register the namespace and prefix your searches.

Attributes vanished. Your converter only reads element text. Switch to explicit Python mapping with .get() for attributes.

Cells contain raw XML fragments. Nested elements got stringified instead of flattened. Expand them explicitly (slash paths in ElementTree, expand arrows in Power Query).

Encoding errors on parse. The XML declares one encoding but uses another — common in legacy exports. Open with the declared encoding overridden, or re-save the file as UTF-8 first.

Huge file crashes the parser. Stream it: iterparse with element clearing, or lxml’s iterparse for constant memory.

Columns differ between runs. Ragged elements union differently as data changes. Pin your column list explicitly in the mapping instead of inferring it.

CDATA sections appear as raw text. CDATA is just a way to embed text with special characters — extract .text normally; the parser already unwrapped it. If markup appears literally, the content was double-escaped upstream.

Numbers sort alphabetically (10 before 2). They imported as text. Select the column and convert: in Excel use Text to Columns with General format, or in pandas use pd.to_numeric.

Duplicate column names. When an element and attribute share a name, or nesting produces collisions, disambiguate with prefixes (prod_id vs order_id) rather than letting the tool number them id, id_1.

Excel shows the CSV as one column. Delimiter mismatch on import — use Data › From Text/CSV and set the delimiter manually, as our CSV format reference notes in its parsing rules.

Frequently Asked Questions

How do I convert XML to CSV?

For simple, regular XML, free online converters work in seconds. For real-world files with attributes, namespaces, or nesting, use Python (ElementTree to parse, pandas to write CSV) for full control, or Excel’s Power Query, which imports XML natively via Data › Get Data › From File › From XML and lets you expand elements visually. Always validate the output: check row counts, confirm attributes survived, and scan for raw markup in cells.

Can Excel open XML files directly?

Yes. Excel can open simple XML as a readable table via File › Open, and Power Query (Data › Get Data › From XML) handles nested XML far better, letting you expand elements into columns before loading.

How do XML attributes become CSV columns?

Attributes (like id=”5″ on an element) are typically flattened into columns named after the attribute, often prefixed to avoid collisions (item_id or item.@id). Decide on one convention and apply it consistently across the file — mixed conventions in one spreadsheet confuse everyone downstream. In Python, read them with element.get(“id”); in Power Query they appear as columns automatically when you expand.

What’s the best Python approach for XML to CSV?

xml.etree.ElementTree (in the standard library) parses the XML; you extract each repeating element into a dict and let pandas write the CSV. For very large files, lxml’s iterparse streams without loading everything into memory.

How do I convert an RSS feed to CSV?

An RSS feed is XML with repeating item elements. Parse it with Python’s feedparser (which handles RSS/Atom dialect differences for you) or ElementTree, extract title, link, and pubDate from each item, and write the rows to CSV — one row per article. For feeds you monitor continuously, schedule the script and append new items by GUID so you never duplicate rows.

Why does my converted CSV have missing columns?

XML elements are often ragged — some records have elements others lack. Good converters union all element names into columns and leave blanks where absent. If columns are missing entirely, the converter may have only read the first record’s structure; switch to an explicit Python mapping where you declare every column up front. Also check for namespaces: a namespaced element looks like a different column name to naive parsers.

CE
csvtoexcel.site team

We build free, private, browser-based file tools. Every guide is written and tested by our editors.