HTML to PPT: Keep text and tables editable
Convert HTML to PPT with a structural reader when the source already contains headings and table cells. Separate semantic articles, CSS grids, and script-generated content before choosing a converter: they need different preparation. The local exports in this guide contain native PowerPoint text and table objects where the source supplies that structure. They are PPTX files. Browser styling and slide layout remain separate work.
Classify the source before choosing an HTML to PPT route
Choose the route from the downloaded HTML, because a browser can display content that a static converter never receives.
Open the saved source in a text editor and search for a distinctive table heading or sentence. Its location determines whether the next step is conversion, restructuring, or retrieving the rendered content.
| What the page contains | How to identify it in the source | Route |
|---|---|---|
| A semantic article with a data table | Headings and values appear inside heading tags and <table> cells |
Use Pandoc directly, or review extracted Markdown before export |
| A grid arranged with CSS | Values appear inside <div> elements, with grid or flex layout rules |
Rebuild a semantic table before using a structural converter |
| Content inserted by JavaScript | The target container is empty. The values appear only in script code or a later request | Obtain the populated content before converting it |
A website may combine them: the article can be static while its comparison table loads after the page opens. Search inside the table itself before treating the entire page as a static document. 17
Pandoc preserves semantic tables as native objects
Use Pandoc directly for a semantic article when your goal is editable content rather than a copy of the webpage's layout.
Pandoc reads HTML into a document structure, then writes that structure to PowerPoint. Its HTML reader recognizes a table as a table node. The PPTX writer maps that node to a native table. Our semantic article produced a PowerPoint table object containing the source headers and data cells. The exported text was also stored in text shapes, without a screenshot of the page. 1237
pandoc input.html --from=html --to=pptx --slide-level=2 \
--metadata=title: --output output.pptx
The slide level makes section headings start slides. Clear the document title metadata when the article's own main heading already supplies the title: our default HTML export repeated it because the page also supplied an HTML <title>. The command above removed that extra title slide while retaining the table. Text before the first section heading can still occupy a slide without a title. 127
Output from our semantic article sample, unedited:
# Debugging Slow Database Migrations
A migration that runs in seconds on a laptop can hold locks for minutes in production. The difference usually comes from table size and from other sessions waiting behind the migration.
Our source placed the article inside <main>, and Pandoc omitted the surrounding navigation, cookie notice, and footer. Removing only that wrapper caused the surrounding text to enter the export. The HTML reader's main-content selection explains the difference. It is not evidence that Pandoc can identify every article on an arbitrary website. 27
| Failure condition | What you will see | Preparation |
|---|---|---|
| Site furniture sits outside an article without a main wrapper | Menu and footer text appear alongside the article | Keep only the intended article container |
| The page has both title metadata and a matching main heading | Repeated title slides in the default export | Clear the redundant document title metadata |
| A table exists only as a CSS layout | Cell words survive as text, with no native table | Restore rows and cells before export |
Markdown exposes clutter before it becomes slide content
Add Markdown as an inspection step when you need to edit the extracted article before building slides.
MarkItDown can expose headings, paragraphs, code, and table rows for that review. In our HTML test it retained the semantic table, but it also retained the website wrapper. Its HTML converter selects the body after removing script and style elements. It does not select the article's main container. 47
from pathlib import Path
from markitdown import MarkItDown
from markitdown.converters import HtmlConverter
converter = MarkItDown(enable_builtins=False, enable_plugins=False)
converter.register_converter(HtmlConverter())
result = converter.convert_local("input.html")
Path("output.md").write_text(result.text_content, encoding="utf-8")
Output from our article-with-navigation sample, unedited:
We use cookies to improve your experience.
Engineering notes from the infrastructure team
Delete the surrounding site text in the Markdown before exporting the deck. Leaving it untouched carried the same clutter into the generated PowerPoint. 7
pandoc output.md --from=gfm --to=pptx --slide-level=2 --output output.pptx
Exporting the unedited intermediate produced a native PowerPoint table. The CSS-grid variant lost its row structure before this step, so the same export command could not turn the loose words back into cells. For content you intend to rearrange substantially, fix the headings and table structure in Markdown, then export. For an already clean semantic article, the direct route avoids that extra handoff. 7
Markovo prepares content before the PPTX step
Use Markovo at the content-extraction stage of an existing API workflow, then send its Markdown to a separate PPTX writer.
Our stored HTML conversion returned the article without its navigation and footer, and kept its semantic table. It retained the same article and table content as the direct Pandoc extraction, while omitting the wrapper text retained by the MarkItDown path. A local Pandoc export of the stored Markdown also contained a native table. This service result covers the shared static article, not the derived CSS or JavaScript variants. 7910
Output from our stored semantic article sample, unedited:
## Find the Locking Statement
Start with the sessions that are active while the migration waits. The wait event column shows whether a session is blocked on a lock or on input and output.
Table text from the direct Pandoc export, its title-cleared variant, and both Markdown-to-PPTX paths, unedited:
| Batch size | Rows per second | Lock wait (ms) |
| --- | --- | --- |
| 1000 | 8200 | 3 |
| 10000 | 9100 | 41 |
| 100000 | 9300 | 620 |
The stored result comes from an authenticated HTML upload, job polling, and a Markdown download. The separate API and CLI examples were not executed for this guide against the service. The API example used a fake HTTP transport. 911121415
The service produces Markdown, not a PowerPoint deck. Export the downloaded Markdown with the earlier Pandoc command. A timeout after upload calls for polling the known job, not repeating the compatibility upload: a repeated request can create another job. 1112
| Boundary | Observed result or limit |
|---|---|
| Shared semantic article | Article, code and table retained without the website wrapper |
| CSS-grid and script variants | Not executed for this guide through the service |
| Need to reproduce webpage coordinates and styling | The measured output is Markdown, not a rendered webpage or finished deck |
For a public HTTPS source, URL to Markdown is a separate authenticated beta import route. It fetches authorized public content without your browser session. Browser-only and login-only pages fall outside that route. The local HTML upload tested here does not establish results for that URL importer. 1113
CSS grids and scripts need a different input
Restore semantic content before exporting when the source has layout cells or script code instead of a populated HTML table.
Our CSS-grid variant kept the original cell words, but both local extraction paths separated them into text blocks. Neither resulting deck contained a native table. The source had no table node for the PowerPoint writer to preserve. 37
Output from our CSS-grid sample, unedited:
Batch size
Rows per second
Lock wait (ms)
The script variant failed earlier: the table values never reached the visible document content parsed by either local path. The source contained a script that would populate an empty container in a browser. A static parser did not run it. Changing only the PPTX output settings cannot recover those missing values. 247
| Symptom | Distinguishing check | Action |
|---|---|---|
| Words remain, but the table became loose text | Values are in layout divs rather than table cells | Rebuild the row and cell structure |
| The table is absent altogether | Values occur only in scripts, or the downloaded container is empty | Retrieve populated HTML or export the table data from its source |
| The content is present but the page's visual arrangement is lost | The requirement is CSS geometry rather than document structure | Evaluate a browser-based DOM conversion path separately |
GX-Alex/html2pptx describes a browser-based DOM-to-PowerPoint route. It was not executed for this guide. Its documented approach is relevant to CSS geometry, but the tests here do not establish its handling of this corpus. 6
Keep the source table semantic, remove page furniture, and generate the deck from that cleaned structure. If the table exists only after script execution, obtaining it comes before choosing the slide writer.
How we tested
The experiment checked source-content retention and native PPTX structure, not browser rendering, slide appearance, or editing in Microsoft PowerPoint.
Markovo is the service we build. It ran through the same corpus and scorer as the other paths; its row is reported in full, including where it scored lower.
Its API result covers only the shared article.html baseline. The derived CSS, script, and no-main samples were not executed through Markovo for this guide. The API snapshot records a successful run created on 2026-10-14. All local conversions, PPTX inspection, and rescoring were performed on 2026-11-04. Its recorded pages_total=1 and pages_ocr=0 describe this HTML run, not an OCR benchmark. Its applicable extraction scores matched Pandoc's baseline, so there are no lower-scoring cells to report. API timing is unavailable. The supplied zero placeholder was discarded. 79
Corpus and boundaries
| File | Representation |
|---|---|
article.html |
Supplied semantic article inside main, with navigation, cookie notice, footer, code and a data table |
article-no-main.html |
Same article with only the main wrapper removed |
article-css-grid.html |
Same table words in div cells arranged by CSS Grid |
article-script-table.html |
Original table assigned into an empty div by JavaScript |
Every sample has a same-named .truth.json derived from the supplied baseline. The expected script-table content is the content after execution. Static omission therefore counts as a missing table. The database throughput numbers are synthetic fixture values, not converter performance measurements. No PDF, scan, OCR, formula, or browser-renderer benchmark was performed. 8
Tools, options and commands
Python 3.11.14, Pandoc 3.11, MarkItDown 0.1.8 with only HtmlConverter registered and with built-ins and plugins disabled, BeautifulSoup 4.15.0, markdownify 1.2.3, python-pptx 1.0.2, and lxml 6.1.3. The stored Markovo response identifies rate-card version 2026-09-19.v3-media-unified, not a converter package version. The stored CLI example was checked against the supplied install contract, not executed for this guide. The stored API example used a fake HTTP transport for multipart submission, running-to-succeeded polling and download. It was not executed for this guide against the service. 1415 891112
PYTHONDONTWRITEBYTECODE=1 tools/lab-venv/bin/python runs/2026-11-04-markovo-html-to-ppt/lab/run_html_to_ppt.py
tools/lab-venv/bin/python tools/lab_score.py --lab runs/2026-11-04-markovo-html-to-ppt/lab --out runs/2026-11-04-markovo-html-to-ppt/lab/scores.json --markdown runs/2026-11-04-markovo-html-to-ppt/lab/scores.md
PYTHONDONTWRITEBYTECODE=1 tools/lab-venv/bin/python runs/2026-11-04-markovo-html-to-ppt/lab/run_html_to_ppt.py --attach-structures
Pandoc's extraction options were --from=html --to=gfm --wrap=none. Direct decks used --from=html --to=pptx --slide-level=2. Markdown decks used --from=gfm --to=pptx --slide-level=2. The title-removal check added --metadata=title: on the baseline only. Full subprocess arguments are in lab/commands.json. The runner derives the variants from the baseline, without network access. 8
Scoring and raw evidence
The unchanged tools/lab_score.py, copied as lab/scorer.py, measures normalized sentence recall, adjacent paragraph-anchor order, same-line table row/header retention, Markdown pipe-table detection, heading text/level, fenced code and specified boilerplate exclusion. It does not measure visual similarity or PPTX editability. Timings are single calls for different stages, not comparable speed estimates. 5
Extraction outputs, directly summarized from lab/scores.json:
| Tool | Sample | Prose | Order | Rows | Header | Pipe | Headings | Levels | Code | Clutter excluded | Seconds |
|---|---|---|---|---|---|---|---|---|---|---|---|
| pandoc-html | article | 1.0 | 1.0 | 1.0 | 1.0 | true | 1.0 | 1.0 | 1.0 | 1.0 | 0.03031 |
| markitdown-html | article | 1.0 | 1.0 | 1.0 | 1.0 | true | 1.0 | 1.0 | 1.0 | 0.0 | 0.004974 |
| pandoc-html | article-no-main | 1.0 | 1.0 | 1.0 | 1.0 | true | 1.0 | 1.0 | 1.0 | 0.0 | 0.0295 |
| markitdown-html | article-no-main | 1.0 | 1.0 | 1.0 | 1.0 | true | 1.0 | 1.0 | 1.0 | 0.0 | 0.004321 |
| pandoc-html | article-css-grid | 1.0 | 1.0 | 0.0 | 0.0 | false | 1.0 | 1.0 | 1.0 | 1.0 | 0.084905 |
| markitdown-html | article-css-grid | 1.0 | 1.0 | 0.0 | 0.0 | false | 1.0 | 1.0 | 1.0 | 0.0 | 0.004702 |
| pandoc-html | article-script-table | 1.0 | 1.0 | 0.0 | 0.0 | false | 1.0 | 1.0 | 1.0 | 1.0 | 0.083453 |
| markitdown-html | article-script-table | 1.0 | 1.0 | 0.0 | 0.0 | false | 1.0 | 1.0 | 1.0 | 0.0 | 0.007004 |
| markovo | article | 1.0 | 1.0 | 1.0 | 1.0 | true | 1.0 | 1.0 | 1.0 | 1.0 | n/a |
PPTX inspection transcripts, scored with the same definitions:
| Tool | Sample | Prose | Order | Rows | Header | Pipe | Headings | Levels | Code | Clutter excluded | Seconds |
|---|---|---|---|---|---|---|---|---|---|---|---|
| pandoc-pptx | article | 1.0 | 1.0 | 1.0 | 1.0 | true | 1.0 | 0.3333 | 0.0 | 1.0 | 0.117628 |
| markitdown-pandoc-pptx | article | 1.0 | 1.0 | 1.0 | 1.0 | true | 1.0 | 0.3333 | 0.0 | 0.0 | 0.052666 |
| pandoc-pptx | article-no-main | 1.0 | 1.0 | 1.0 | 1.0 | true | 1.0 | 0.3333 | 0.0 | 0.0 | 0.108318 |
| markitdown-pandoc-pptx | article-no-main | 1.0 | 1.0 | 1.0 | 1.0 | true | 1.0 | 0.3333 | 0.0 | 0.0 | 0.104009 |
| pandoc-pptx | article-css-grid | 1.0 | 1.0 | 0.0 | 0.0 | false | 1.0 | 0.3333 | 0.0 | 1.0 | 0.05282 |
| markitdown-pandoc-pptx | article-css-grid | 1.0 | 1.0 | 0.0 | 0.0 | false | 1.0 | 0.3333 | 0.0 | 0.0 | 0.119265 |
| pandoc-pptx | article-script-table | 1.0 | 1.0 | 0.0 | 0.0 | false | 1.0 | 0.3333 | 0.0 | 1.0 | 0.115801 |
| markitdown-pandoc-pptx | article-script-table | 1.0 | 1.0 | 0.0 | 0.0 | false | 1.0 | 0.3333 | 0.0 | 0.0 | 0.133955 |
| pandoc-pptx-no-title | article | 1.0 | 1.0 | 1.0 | 1.0 | true | 1.0 | 0.3333 | 0.0 | 1.0 | 0.055075 |
| markovo-pandoc-pptx | article | 1.0 | 1.0 | 1.0 | 1.0 | true | 1.0 | 0.3333 | 0.0 | 1.0 | 0.067849 |
The PPTX rows score a serialized inspection transcript. Its headings are all emitted with a single hash, and code text is not fenced, so its heading-level and code-fence scores reflect the transcript format. They do not show that PowerPoint lost the original code text. The object inspection separately counts native tables using python-pptx and cross-checks them against a:tbl elements in slide XML. Those mechanically generated counts are attached under pptx_structures in lab/scores.json. The scorer's original cells remain unchanged. 578
| Tool | Sample | Slides | Native tables | Text shapes | Picture shapes |
|---|---|---|---|---|---|
| pandoc-pptx | article | 5 | 1 | 7 | 0 |
| markitdown-pandoc-pptx | article | 5 | 1 | 8 | 0 |
| pandoc-pptx | article-no-main | 6 | 1 | 9 | 0 |
| markitdown-pandoc-pptx | article-no-main | 5 | 1 | 8 | 0 |
| pandoc-pptx | article-css-grid | 5 | 0 | 7 | 0 |
| markitdown-pandoc-pptx | article-css-grid | 5 | 0 | 7 | 0 |
| pandoc-pptx | article-script-table | 5 | 0 | 7 | 0 |
| markitdown-pandoc-pptx | article-script-table | 5 | 0 | 7 | 0 |
| pandoc-pptx-no-title | article | 4 | 1 | 6 | 0 |
| markovo-pandoc-pptx | article | 4 | 1 | 6 | 0 |
Raw extraction files are lab/pandoc-html/<sample>.md, lab/markitdown-html/<sample>.md, and lab/markovo/article.md. PPTX transcripts and raw slide XML text are under lab/pandoc-pptx/, lab/markitdown-pandoc-pptx/, lab/pandoc-pptx-no-title/, and lab/markovo-pandoc-pptx/. The inspected .pptx and input .html files stay local. The publishable baseline source is lab/corpus/article-source.txt. Copy it to lab/corpus/article.html before running the script. Ground truths, the runner, stored API response and score tables accompany the evidence. 78910
Sources
- Pandoc 3.11 manual
- Pandoc 3.11 HTML reader: readHtml, extractMain, pHead
- Pandoc 3.11 PowerPoint writer: blockToShape
- MarkItDown 0.1.8 HtmlConverter source
- Unchanged copy of tools/lab_score.py
- GX-Alex/html2pptx README: not executed
- Measured scores and mechanically attached native PPTX structures
- Local experiment runner and object inspector
- Stored Markovo API response: created 2026-10-14
- Stored Markovo Markdown output for the shared article
- Supplied Markovo developer.md snapshot
- Supplied Markovo install.md snapshot
- Supplied Markovo keyword and capability ownership snapshot
- Executed snippet and fake-transport checks
- Local snippet-check runner, with no real API call


