Convert XHTML & EPUB Chapters to Markdown
xhtml to md for EPUB chapters, DocBook and Javadoc output, and CMS exports that never left the 2000s. The document is parsed with the browser’s own HTML parser — so a malformed file is recovered rather than rejected — then DOMPurify strips the presentational and executable layer and Turndown maps what remains onto CommonMark and GFM, entirely in your browser.Processing happens 100% in your browser — nothing is uploaded.
How to convert XHTML to Markdown
- Provide the document: drop an .xhtml or .html file onto the dropzone, or paste the markup into the editor. For an EPUB, rename it to .zip, unzip it, and use the chapter files inside.
- Local processing: the markup is parsed, sanitised with DOMPurify and converted by Turndown in a Web Worker — no upload and no asset fetching.
- Copy or download: copy the Markdown or save it as a .md file for your docs-as-code pipeline, Hugo/Jekyll content directory or vault.
Why Convert XHTML to Markdown with MD Convert?
Lenient parsing where a strict XML parser fails
Real XHTML routinely breaks the rules it declares: an unescaped &, an unclosed <p>, a stray attribute. A conforming XML parser must reject the whole document for any one of them. Parsing through the browser's HTML parser recovers the document instead, which is what you want for a file you did not write a decade ago. Self-closing <br />, <img /> and <hr /> are handled natively.
Presentational debt removed, not translated
style attributes and <style> elements are stripped, along with <form>, <input> and <button>, and <font>, <center> and bare <span class> wrappers leave no trace — there is no Markdown construct for them. That removal is the point of the conversion rather than a loss of fidelity.
Structural semantics mapped precisely
Headings become ATX (# … ######), <strong> and <b> become **bold**, <em> and <i> become _italic_, lists keep their nesting, <pre> becomes a fenced block rather than an indented one, and tables become GFM pipe tables whose first row is treated as the header — a pipe table without a delimiter row is not a table at all in GFM.
Sanitisation as a security control
Inherited markup is treated as hostile input: <script> elements, on* event-handler attributes and javascript: URLs are removed before conversion, so nothing executable reaches the preview pane or survives into the Markdown you paste elsewhere. That matters most for exactly these files — old pages of unknown provenance.
XHTML to Markdown: Before and After
Self-closing void elements are handled natively, and the presentational layer — `xmlns`, `style`, `<span class>`, `<font>` — is dropped rather than translated. Structural semantics survive: the heading, the emphasis and the code element all map onto CommonMark.
<div xmlns="http://www.w3.org/1999/xhtml">
<h2 style="color:#c00">Configuration</h2>
<p>Set <code>retries</code> to <em>3</em>.<br />
Restart required.</p>
<img src="diagram.png" alt="Retry flow" />
</div>## Configuration
Set `retries` to _3_.
Restart required.
Understanding the XHTML Format: XHTML 1.1 (W3C Recommendation)
- Format
- Extensible HyperText Markup Language
- Specification
- XHTML 1.1 (W3C Recommendation)
- Media type
application/xhtml+xml- Parser used
- Turndown + DOMPurify
XHTML is HTML reformulated as XML. The vocabulary is HTML's — headings, paragraphs, lists, tables, anchors — but the syntax rules are XML's: every element must be closed, void elements must self-close (`<br />`, `<img />`, `<hr />`), attribute values must be quoted, tag names are lowercase, and the document declares a namespace with `xmlns`.
It survives in exactly the places that adopted it during its 2000s peak and never migrated: EPUB, whose content documents are XHTML by specification, plus DocBook and DITA output, older Javadoc and Doxygen builds, and CMS exports from that era. Those files are also where the worst presentational debt lives — `<font>` elements, `style` attributes on every paragraph, nested layout tables, and `<span class="c17">` wrappers with no surviving stylesheet.
Conversion here is deliberately lenient about syntax and strict about content. The markup is parsed with the browser's own HTML parser, which recovers from the unclosed tag or stray ampersand that would make a real XML parser reject the document outright, then DOMPurify removes everything that is presentation or behaviour, and Turndown maps what remains onto CommonMark and GFM.
Lenient parsing, strict cleanup
A strict XML parser is the wrong tool for files like these, and this is the detail most converters get backwards. Real-world XHTML frequently violates the very rules it declares — an unescaped `&`, an unclosed `<p>`, a stray attribute — and a conforming XML parser must reject the whole document for any one of them. Parsing through the browser's HTML parser instead means the document is recovered and converted rather than refused, which is what you want when the file is a decade old and you did not write it.
XHTML's syntactic strictness is nonetheless helpful in one direction: because void elements self-close, `<br />`, `<img />` and `<hr />` are unambiguous, and the HTML parser handles that spelling natively. Nothing needs to be rewritten before conversion.
The namespace declaration itself is discarded. `xmlns` carries no meaning once the content is Markdown, and namespaced elements from other vocabularies — MathML or SVG embedded in an EPUB chapter — are removed by the sanitiser while their text content is kept, since Markdown has no way to express them.
What maps onto CommonMark, and what gets dropped
The structural vocabulary maps directly. `<h1>`–`<h6>` become ATX headings (`#` through `######`), `<strong>` and `<b>` become `**bold**`, `<em>` and `<i>` become `_italic_`, `<blockquote>` becomes `>`, `<ul>`/`<ol>` become `-` and `1.` lists with nesting preserved, `<a>` becomes an inline link, and `<img>` becomes an image with its `alt` text carried across.
Code is handled at both levels: an inline `<code>` becomes backticked text, and a `<pre>` block becomes a fenced block with triple backticks rather than the four-space indented form, because fenced blocks accept a language hint and survive nesting inside list items.
Tables become GFM pipe tables. The first row is treated as the header whether its cells are `<th>` or `<td>` — a pipe table without a delimiter row is not a table at all in GFM — and pipes inside cell text are escaped so a value cannot split its row. Layout tables, the ones used for page structure rather than data, will convert to a table too; those are worth deleting in the source first.
Presentational markup is removed rather than approximated. `style` attributes and `<style>` elements are stripped, as are `<form>`, `<input>` and `<button>`, and `<script>`, `<noscript>` and `<iframe>` are dropped with their contents. `<font>`, `<center>` and bare `<span class="…">` wrappers leave no trace because there is no Markdown construct that corresponds to them — that removal is the point of the conversion, not a loss.
EPUB chapters and legacy documentation sets
An EPUB is a ZIP archive whose content documents are XHTML by specification. Unzip the `.epub` — renaming it to `.zip` is enough — and each chapter file inside `OEBPS/` or `EPUB/` converts cleanly here, one chapter to one Markdown file. That is the practical route for re-editing a book you own, extracting quotes with structure intact, or moving your own published title into a Markdown toolchain.
Legacy documentation sets behave similarly but need one decision first: generated navigation. Javadoc, Doxygen and older DocBook output wrap every page in header, footer and sidebar markup, and converting the whole file produces a Markdown document whose first screen is a navigation list. Trimming to the content element before conversion — usually a single `<div>` — is the difference between a clean result and one you edit for ten minutes.
Entity references are resolved during parsing, so ` `, `—` and numeric references such as `—` arrive in the output as the characters they denote rather than as escape sequences. Non-breaking spaces are worth a search-and-replace afterwards: they are invisible in the output but they are not ordinary spaces, and they can affect how a renderer wraps a line.
Sanitised and converted in your browser
Legacy XHTML often arrives from somewhere sensitive — an internal wiki export, a client's CMS dump, a manuscript under embargo. Every stage runs locally: the file is read with the FileReader API, DOMPurify and Turndown run in a Web Worker in your own browser, and no request carries the content anywhere. There is no upload, no account and no server-side record.
Sanitisation is a security control here, not only a cleanup pass. Inherited HTML is treated as hostile input: `<script>` elements, `on*` event-handler attributes and `javascript:` URLs are removed before conversion, so nothing executable can reach the preview pane or survive into the Markdown you paste elsewhere. This matters most for exactly the files people convert here — old pages of unknown provenance.
The remaining practical constraint is that images stay as references. An `<img src="diagram.png" />` becomes ``, which resolves only if you copy the image alongside the Markdown — the converter reads one document at a time and never fetches a linked asset, which is also why conversion is instant and offline.
Known limitations of XHTML 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 XHTML programmatically:
- Parsing is lenient rather than strict: the browser's HTML parser recovers from malformed XHTML instead of rejecting it, so a namespace or well-formedness error will not be reported to you.
- Inline CSS is discarded, not translated. Colour, font and alignment have no Markdown equivalent.
- Embedded MathML and SVG are removed with their text content kept, since neither has a CommonMark representation.
- Images remain relative references. Linked assets are never fetched, so copy them alongside the output.
- Layout tables convert to data tables. Delete them in the source if they were only there for page structure.
- Generated navigation, headers and footers convert along with the content; trim to the content element first for Javadoc- or DocBook-style output.
- Class and id attributes are dropped, so anchor links that targeted an id elsewhere in the document will no longer resolve.
Who Converts XHTML to Markdown?
EPUB & Ebook workers
Extracting chapter XHTML from an unzipped EPUB into clean Markdown for re-editing, translation or republishing.
Documentation Engineers
Migrating legacy DocBook, DITA or Javadoc HTML output into a Markdown-based docs-as-code pipeline.
Technical Writers
Stripping a decade of inline font and colour markup out of inherited pages without hand-editing each one.
Static-site migrants
Moving CMS-era XHTML exports into Hugo, Jekyll or Astro content files that build without embedded HTML.
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.
XHTML to Markdown — FAQ
Can I convert EPUB chapters with this?
Yes. An EPUB is a ZIP archive whose content documents are XHTML by specification, so rename the .epub to .zip, unzip it, and convert the chapter files inside OEBPS/ or EPUB/ — one chapter to one Markdown file. That is the practical route for re-editing or republishing a book you own.
Is XHTML parsed strictly as XML?
No, deliberately. Parsing goes through the browser's HTML parser, which recovers from well-formedness errors instead of rejecting the document the way a conforming XML parser must. The trade-off is that you will not be told about a namespace or well-formedness problem — you get a converted document rather than an error.
What happens to inline CSS, MathML and SVG?
Inline CSS is discarded, since colour, font and alignment have no Markdown equivalent. Embedded MathML and SVG are removed with their text content kept, because neither has a CommonMark representation. If a formula matters, extract it to LaTeX before converting.
Why does my Javadoc or DocBook output start with a navigation list?
Because generated documentation wraps every page in header, footer and sidebar markup, and all of it is real content to a converter. Trim the source to the content element — usually a single <div> — before converting. That one step is the difference between a clean result and ten minutes of editing.
Are images and linked assets included?
Images become standard Markdown image references with their alt text preserved, but the files themselves are never fetched — the converter reads one document at a time, which is also why it works offline and instantly. Copy the referenced images alongside the Markdown so the relative paths resolve.