Convert XML to Markdown Lists & Tables
.xml document into readable Markdown with fast-xml-parser: the element hierarchy becomes an indented bullet list, repeated sibling records become a GFM table, and attributes are kept inline. The whole tree is parsed in your browser — nothing is uploaded.Processing happens 100% in your browser — nothing is uploaded.
No schema means no semantic mapping
49.00 renders as 49 and zero-padded codes can lose their padding. Attribute values are left exactly as written.How to convert XML to Markdown
- Input: drop your .xml file onto the dropzone, or paste the document straight into the editor.
- Process: fast-xml-parser builds the tree locally, then each node is rendered as a nested bullet or, for flat repeated siblings, as a GFM table.
- Copy: send the Markdown to your clipboard or save it as a .md file.
Why Convert XML to Markdown with MD Convert?
Repeated records become GFM tables
When a run of sibling elements shares a name and every child is a scalar value, the group is emitted as a pipe table with one row per sibling and the union of fields as columns. Missing optional fields become empty cells rather than shifting values into the wrong column.
Hierarchy becomes indented bullet lists
Anything genuinely nested stays nested: each element becomes a bullet with its children indented beneath it, to arbitrary depth. Elements carrying only character data collapse onto a single labelled line instead of spawning a redundant child node.
Attributes are preserved, not discarded
In XML the identifier is more often an attribute than a child element, so attributes are rendered inline in parentheses after the element name — and as their own at-prefixed columns in table output, where they cannot collide with a same-named child element.
Schema-agnostic and 100% local
No tag name is given special meaning, so custom schemas convert as predictably as standard ones. fast-xml-parser runs inside your browser, which is what makes it safe for service payloads, signed messages, and configs holding endpoints.
XML to Markdown: Before and After
One document, both renderings. The scalar element becomes a labelled line, and the repeated sibling elements are detected as a record set and emitted as a pipe table — with the attributes as their own columns.
<inventory>
<updated>2026-08-31</updated>
<item sku="A-100" stock="12">
<name>Wireless keyboard</name>
<price>49.00</price>
</item>
<item sku="A-101" stock="0">
<name>Mechanical keyboard</name>
<price>119.00</price>
</item>
</inventory>- **inventory**
- **updated**: 2026-08-31
- **item** — 2 entries
| name | price | @sku | @stock |
| --- | --- | --- | --- |
| Wireless keyboard | 49 | A-100 | 12 |
| Mechanical keyboard | 119 | A-101 | 0 |Understanding the XML Format: W3C XML 1.0 (Fifth Edition)
- Format
- Extensible Markup Language
- Specification
- W3C XML 1.0 (Fifth Edition)
- Media type
application/xml- Parser used
- fast-xml-parser
XML is a meta-format, which is what makes converting it to Markdown structurally different from converting HTML. HTML has one fixed vocabulary — a heading element means a heading everywhere on the web — so a converter can hard-code a mapping. XML defines only the syntax of elements, attributes, namespaces and entities; the vocabulary is whatever the document's author or schema decided. There is no way to know whether an element called `entry` should become a heading, a list item, a table row or nothing at all, because the answer depends on a schema the converter has never seen.
What is universally available is the shape of the tree. Every well-formed XML document is a single root element containing an ordered hierarchy of child elements, each optionally carrying attributes and character data. That hierarchy maps naturally onto two Markdown constructs and only two: an indented bullet list, which preserves arbitrary depth, and a pipe table, which is the correct rendering when a level of the tree is really a set of uniform records rather than a nesting.
So the converter chooses between those two per node. fast-xml-parser turns the document into a plain object tree in your browser, and the renderer walks it: an element with element children becomes a bullet with nested bullets beneath it, an element with only character data becomes a single labelled line, attributes are appended inline in parentheses, and a run of repeated sibling elements whose own children are all scalar is emitted as a GitHub-Flavored Markdown table with one row per sibling. Nothing is inferred about the meaning of your tag names, which is precisely why the output is predictable across schemas you invented and schemas you inherited.
Lists or tables: how the renderer decides
The decision is made per node and rests on a single test: are these repeated siblings uniformly flat? A group qualifies as a record set when it contains at least two sibling elements with the same name, each of those siblings is an element rather than bare text, and every child of every sibling is scalar — a value, not another subtree. If any sibling contains a nested element or a nested repetition, the group fails the test and the whole group is rendered as bullets instead.
That asymmetry is intentional. A table that quietly drops a nested field is data corruption wearing the costume of clean output; a bullet tree that is longer than it needed to be is merely verbose. Erring toward the lossless rendering means the only cost of a false negative is a few extra lines, while the cost of a false positive would be a silently truncated document.
Column order follows the parser rather than the source text: element children come first in document order, then attribute columns, prefixed with an at sign so an attribute named `id` cannot collide with a child element also named `id`. Columns are the union across all siblings, so a record missing an optional field gets an empty cell rather than shifting every subsequent value one column left. Cell values have their whitespace collapsed and any literal pipe escaped, which is what stops one value containing a pipe from splitting its row.
Tables are only emitted near the top of the document, where they can sit at the left margin. A pipe table indented three or four levels into a bullet list is not portable — renderers disagree about whether it is a table or a code block — so deeply nested record sets deliberately stay as bullets. If a table is what you need from a deep node, extracting that subtree and converting it on its own gives you one.
Attributes, character data and mixed content
In XML the identifier is more often an attribute than a child element, so discarding attributes would gut most real documents. They are rendered inline in parentheses immediately after the element name, which keeps a bullet tree scannable while remaining lossless: an element with two attributes and no children still produces a complete line rather than a bare label.
An element that carries only character data is collapsed onto one line as a labelled value, rather than producing a redundant child node for its own text. The same collapse applies when the element has both attributes and text, so a titled element with a language attribute renders as a single readable line with the attribute in parentheses and the text after the colon. Character data wrapped in a CDATA section is unwrapped and treated as text, which is what makes documents that embed markup or scripts inside their values convert rather than emit an object placeholder.
Mixed content — text interleaved with child elements inside the same parent — is where any tree-to-list projection is weakest. The parser separates the text from the elements, so the interleaving order is not recoverable and the text appears as its own entry alongside the child elements rather than woven between them. Documents that are genuinely narrative in this way, such as DocBook or a JATS article, are better served by an XSLT transform written against their schema, or by converting the rendered HTML output instead.
Namespaces, entities and well-formedness
Namespace prefixes are preserved verbatim as part of element names, so a prefixed element keeps its prefix in the Markdown label. This is a deliberate non-resolution: the alternative is expanding each prefix to its namespace URI, which is technically more correct and considerably less readable, and which discards the prefix your source document and your colleagues actually use to refer to the element.
Predefined entities and numeric character references are resolved during parsing, so an escaped ampersand or angle bracket becomes the literal character in the output. External and custom entity declarations from a document type definition are not resolved — this is standard, safety-relevant behaviour in browser-side parsers, because resolving external entities is the mechanism behind the classic external-entity injection class of attack. A document relying on custom entity definitions will show unresolved references, and the fix is to expand them before conversion.
Well-formedness is not optional in XML the way tag soup is tolerated in HTML. An unclosed element, a mismatched case in a closing tag, an unescaped ampersand in a value, or a stray control character will fail the parse, and the converter surfaces the parser's own message rather than guessing at a repair. That refusal is the correct behaviour: a heuristic fix-up would produce output that looks plausible while quietly reinterpreting your structure.
One coercion is worth knowing about because it is visible in the sample above: values that look numeric are parsed as numbers, so a price written as `49.00` renders as `49` and a leading-zero identifier can lose its zeros. If exact lexical form matters — a version string, a zero-padded code, a fixed-precision amount — check those fields in the output. Attribute values are not coerced and stay as written.
Local parsing, large documents and the Evernote path
Parsing runs entirely in your browser, in a worker off the main thread, so the interface stays responsive and the document is never transmitted anywhere. That matters more for XML than for most formats, because the XML sitting on a developer's disk is disproportionately likely to be a service payload with customer records in it, an integration log, a signed message or a configuration file containing endpoints and credentials. None of that should be pasted into a hosted converter, and here it cannot be, because there is no upload step to paste it into.
There is no imposed size limit. The whole document is parsed into an object tree in memory, so peak memory scales with document size rather than staying flat the way page-at-a-time PDF extraction does — a very large export is bounded by the tab's available memory rather than by a server's upload cap. In practice multi-megabyte documents convert without difficulty; multi-hundred-megabyte data dumps are a job for a streaming parser in a script, and that is the honest recommendation rather than a limitation to work around.
The same engine handles Evernote exports, which are XML with an HTML-like dialect inside them. That path is detected from the root element and behaves entirely differently: note bodies are converted as markup, checkboxes become task-list items, and tags and timestamps are lifted into frontmatter. If you are migrating a notebook rather than reading a data file, the dedicated Evernote converter is the page you want, and it uses this same local parser underneath.
Known limitations of XML to Markdown conversion
Being explicit about what a converter cannot do saves you a wasted upload. These are the boundaries of what is recoverable from XML programmatically:
- Tag names carry no meaning to the converter. Nothing becomes a heading, because no schema-independent rule can tell a title element from a data field.
- Numeric-looking element values are coerced to numbers, so `49.00` renders as `49` and zero-padded codes can lose their padding. Attribute values are untouched.
- Record-set tables are only emitted near the top of the document; deeply nested repetitions stay as bullet lists, because indented pipe tables are not portable GFM.
- Cells that would need to hold a nested structure disqualify the whole group from table rendering — deliberately, since a table that drops a field is worse than a verbose list.
- Mixed content loses its interleaving: text and child elements are separated rather than woven together in document order.
- Namespace prefixes are preserved as written and not expanded to their namespace URIs.
- External and custom entities declared in a document type definition are not resolved; references appear unexpanded.
- Malformed XML fails outright with the parser's error rather than being repaired heuristically.
- The whole tree is held in memory, so extremely large exports are better handled by a streaming parser in a script.
Who Converts XML to Markdown?
Backend & integration developers
Reading a legacy service payload or a vendor feed as something diffable and reviewable, instead of squinting at one line of unformatted markup.
Data engineers
Turning a repeated-record export into a Markdown table for a ticket, a runbook or a pull-request description, without loading it into a database first.
Documentation owners
Publishing configuration files, sitemaps and manifests as readable reference pages in a Markdown docs site.
Retrieval pipeline engineers
Flattening structured feeds into consistent Markdown so one chunker handles every source, with keys and values kept adjacent for context.
XML to Markdown: Comparing the Practical Approaches
| Method | Privacy | Record sets as tables | Schema awareness | Setup | Batch / automation | Cost |
|---|---|---|---|---|---|---|
| This tool (fast-xml-parser) | Highest — parsed in the tab, never uploaded | Yes, for uniformly flat siblings | None by design — predictable across schemas | None — paste or drop a file | One document at a time | Free |
| XSLT stylesheet | High — runs locally | Exactly as you specify | Total — you encode your schema | Write and maintain a stylesheet | Strong — scriptable | Free, plus your time |
| Pandoc CLI | High — runs locally | No; XML is not a general Pandoc input | Only for formats it knows (DocBook, JATS) | Local install | Strong | Free |
| A custom script (Python or Node parser) | High — your infrastructure | Whatever you implement | Whatever you implement | Code plus dependencies | Strong | Free, plus your time |
| Pasting the document into a chat model | Low — the payload becomes a prompt | Usually, but values may be silently altered | Inferred, and confidently wrong at times | None | Poor | Per-token |
How You Can Verify the Privacy Claim
Zero server upload
Conversion runs inside your browser tab. Open DevTools, switch to the Network panel, and convert a file: for every format except URL to Markdown you will see no request carrying your document — because there is no endpoint to send it to.
Off the main thread
Heavy parsing is dispatched to a Web Worker, so a 500-page PDF or a large spreadsheet never freezes the interface. Everything is plain JavaScript — no native plugin, no WebAssembly toolchain, nothing to install.
Nothing to sign up for
No login, no quota, no paywall, and no tracking tied to your files. Analytics are cookieless and aggregate only. Read the privacy policy for the full data-flow breakdown, including the one proxied exception.
The parsers doing the work
No proprietary black box: each format is handled by a widely audited open-source library, running client-side at the version pinned in our lockfile.
XML to Markdown — FAQ
When do repeated elements become a table instead of a list?
When at least two siblings share a name and every one of their children is a scalar value. A single nested child anywhere in the group disqualifies it and the whole group stays as bullets — a verbose list is better than a table that silently drops a field. Tables are also only emitted near the top of the document, because indented pipe tables are not portable GFM.
How are XML attributes represented in the Markdown?
Inline, in parentheses after the element name, so a bullet stays readable while remaining lossless. In table output each attribute becomes its own column prefixed with an at sign, which keeps it distinct from a child element of the same name.
Are namespaces and CDATA sections handled?
Namespace prefixes are preserved verbatim as part of element names rather than expanded to namespace URIs, which keeps the labels readable and matches how your team refers to the elements. CDATA content is unwrapped and treated as ordinary character data.
What happens if the XML is malformed?
The parse fails and the parser's own error is surfaced, rather than a heuristic repair being attempted. Unclosed tags, mismatched closing tags, and unescaped ampersands are the usual causes. External and custom entities from a DTD are deliberately not resolved, since resolving them is the external-entity injection attack surface.
Is the output usable in Obsidian or a RAG pipeline?
Yes. Bullet trees and pipe tables are standard GFM, so a vault renders them directly, and keys stay adjacent to their values so a chunk retains its own context. For notebook migration from an Evernote .enex export, use the dedicated Evernote converter — it shares this same local parser but reads note metadata.