Metadata-Version: 2.5
Name: marcato
Version: 0.2.0
Summary: Deterministic HTML-to-Markdown conversion on lxml.
Project-URL: Homepage, https://getanalog.io
Author-email: Marcus Campbell <marcus@getanalog.io>
License: MIT
License-File: LICENSE
License-File: THIRD-PARTY-NOTICES.md
Keywords: converter,deterministic,html,lxml,markdown
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Documentation
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: lxml<7,>=5.2
Description-Content-Type: text/markdown

# Marcato

**Marcato by Analog** converts HTML to Markdown with one parser and one narrow
dependency: [`lxml`](https://lxml.de/).

```bash
pip install marcato
```

```python
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](https://github.com/matthewwithanm/python-markdownify/tree/1.2.3)
by Matthew Tretter and has since diverged. The complete upstream copyright and
MIT license notice ships in [`THIRD-PARTY-NOTICES.md`](THIRD-PARTY-NOTICES.md).

Marcato itself is licensed under the [MIT License](LICENSE).
