Marcato
Marcato by Analog converts HTML to Markdown with one parser and one narrow
dependency: lxml.
pip install marcato
from marcato import html_to_markdown
markdown = html_to_markdown("<h1>Hello</h1><p>Good to meet you.</p>")
Marcato preserves common document structure including headings, links, lists, tables, code, images, advisory blocks, and authored inline emphasis. Its single rendering policy defines an intentional GFM-compatible presentation.
Input and rendering contract
html_to_markdown() accepts plain text, an HTML fragment, a full HTML
document, or an empty string. Malformed HTML remains supported when
lxml can recover it; unrecoverable markup raises ValueError. Given the same
input, Marcato version, and supported lxml version, recovery and rendering
are deterministic. Non-string input raises TypeError.
Presentation
Marcato uses ATX headings (# through ######), one stable - marker
for unordered lists at every depth, inline links, and unwrapped prose. Literal
asterisks and underscores are escaped, while broad punctuation escaping stays
off; generated links, images, tables, and other Markdown constructs always
escape the grammar boundaries they own.
Normal-flow horizontal whitespace collapses across inline child boundaries, including around omitted hidden content and text synthesized from authored accessibility metadata. Browser-collapsible whitespace at the document edges is omitted. Preformatted content, Markdown indentation, and interior hard line breaks remain unchanged.
Blocks and regions
Marcato preserves boundaries between standard HTML block containers, including
main, article, section, aside, header, footer, nav, figure, and
address. These containers are structurally transparent: they separate their
authored contents without adding labels or deciding which page regions matter.
Callers remain responsible for selecting the HTML region Marcato receives.
Block containers authored inside a link retain their separation without
terminating the Markdown link label. Phrasing containers such as span,
abbr, and time remain inline and do not introduce new boundaries.
Code languages
For a block <pre> element, Marcato emits a syntax-highlighting language when
it has one direct <code> child whose classes contain exactly one
language-TOKEN entry. TOKEN must begin with an ASCII letter or digit and
may then contain ASCII letters, digits, _, ., +, #, or -. Other
class names are not interpreted as languages, and unsafe or ambiguous values
are omitted.
Marcato chooses backtick or tilde fences that cannot collide with the code content.
Links and images
Marcato serializes authored link, image, and video destinations without interpreting or rewriting them. It retains compact inline Markdown for simple destinations and uses the equivalent angle-bracket form when whitespace, control characters, or unbalanced parentheses would otherwise corrupt the construct. Titles, link text, and image alt text are escaped at their Markdown grammar boundaries so page-authored characters cannot replace a destination or terminate a link early.
Matching link text is emitted as an autolink only when the destination is an eligible absolute URI. Relative and empty destinations retain ordinary inline link syntax. These rules are serialization only: Marcato does not resolve, allowlist, fetch, or assess destinations.
An icon-only local link whose fragment target is declared by its surrounding heading is treated as that heading's permalink affordance. Marcato keeps the heading text once instead of synthesizing a duplicate link from accessibility metadata. Visible heading links and icon-only links to other targets remain authored content.
When a local link that remains in the Markdown points to a custom target declared by a heading or its descendants, Marcato preserves that relationship with a GFM custom anchor immediately before the heading. Unreferenced IDs, targets outside headings, and suppressed permalink affordances add no anchor markup.
Tables
Marcato emits a GFM table when its cells contain inline content, it has at
most one header row, and it does not use spanning or nested tables. Pipes in
cell content are escaped, <br> remains an inline break, and shorter rows
receive empty trailing cells so every authored value remains in the table.
Headerless tables receive an empty header rather than assigning header
semantics to the first authored row.
A table with colspan, rowspan, multiple header rows, block content in a
cell, or another table inside it cannot be represented faithfully as a GFM
table. Marcato emits its caption followed by each non-empty cell as ordinary
Markdown blocks in source order. This linear fallback preserves authored
content without inventing a rectangular relationship. A page-authored
accessible name still fills an otherwise silent cell.
Compatibility
Marcato's rendered Markdown is its primary public contract. Within a 0.x
minor line, patch releases do not intentionally change rendered bytes for an
existing HTML input. Intentional rendering changes, including bug fixes that
alter output, begin a new minor line.
The same Marcato and lxml versions produce deterministic output. Marcato
bounds the lxml major versions it supports; dependency-induced output drift
within that range is treated as a compatibility defect.
Attribution
Marcato originated as an lxml port of
markdownify 1.2.3
by Matthew Tretter and has since diverged. The complete upstream copyright and
MIT license notice ships in THIRD-PARTY-NOTICES.md.
Marcato itself is licensed under the MIT License.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file marcato-0.2.0.tar.gz.
File metadata
- Download URL: marcato-0.2.0.tar.gz
- Upload date:
- Size: 38.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a46ce489c7bba9dff2dcad07e54a09a082a93f67d54c009049207b527de4773d
|
|
| MD5 |
bed412ee7421aa89d88b43f568360d15
|
|
| BLAKE2b-256 |
a30835bcbd5f83b1898cbe4d3c665d8a87b84b6b72d87a052190230fafbd1ed9
|
Provenance
The following attestation bundles were made for marcato-0.2.0.tar.gz:
Publisher:
release-marcato-python.yml on getanalog/monorepo
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
marcato-0.2.0.tar.gz -
Subject digest:
a46ce489c7bba9dff2dcad07e54a09a082a93f67d54c009049207b527de4773d - Sigstore transparency entry: 2481226628
- Sigstore integration time:
-
Permalink:
getanalog/monorepo@bdbd9eafe911e4876c1592ef2d9d676fd7d02161 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/getanalog
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release-marcato-python.yml@bdbd9eafe911e4876c1592ef2d9d676fd7d02161 -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file marcato-0.2.0-py3-none-any.whl.
File metadata
- Download URL: marcato-0.2.0-py3-none-any.whl
- Upload date:
- Size: 28.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
47aace4becce8d8d9988ea5f44fcd9195016cfecbb118d41c15c14b85e6eaa5f
|
|
| MD5 |
b6ea66945a37896a1a780b3ba13a7988
|
|
| BLAKE2b-256 |
0d9e8adb070d046c136d9f1c02f3efa4ec0671cb17660e5410d55c1c13f824e6
|
Provenance
The following attestation bundles were made for marcato-0.2.0-py3-none-any.whl:
Publisher:
release-marcato-python.yml on getanalog/monorepo
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
marcato-0.2.0-py3-none-any.whl -
Subject digest:
47aace4becce8d8d9988ea5f44fcd9195016cfecbb118d41c15c14b85e6eaa5f - Sigstore transparency entry: 2481226750
- Sigstore integration time:
-
Permalink:
getanalog/monorepo@bdbd9eafe911e4876c1592ef2d9d676fd7d02161 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/getanalog
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release-marcato-python.yml@bdbd9eafe911e4876c1592ef2d9d676fd7d02161 -
Trigger Event:
workflow_dispatch
-
Statement type: