###########################
Export a tree to Markdown
###########################
Turn a node into `GitHub-Flavored Markdown `_ with :meth:`~turbohtml.Node.to_markdown`,
so a scraping script ends with Markdown instead of tag soup.
:meth:`~turbohtml.Node.to_markdown` renders a node and its subtree as GitHub-Flavored Markdown (headings, lists, links,
emphasis, code, blockquotes, images, and pipe tables), collapsing runs of whitespace the way normal flow lays them out.
It is a one-call replacement for the ``scrape`` → ``Markdown`` step that html2text or markdownify would do, with no
second dependency and the whole walk in C:
.. testcode::
import turbohtml
page = turbohtml.parse(
"
Recipe
A quick loaf.
"
""
"Rest 1 hour.
"
)
print(page.to_markdown())
.. testoutput::
# Recipe
A **quick** loaf.
- flour
- water
> Rest 1 hour.
Call it on any node to export just that subtree (``article.to_markdown()``). The output is opinionated GFM: ATX
headings, ``-`` bullets, fenced code blocks, inline links, and ``*``/``**`` emphasis.
A :class:`~turbohtml.Markdown` configuration object covers the markdownify and html2text surface, so a migration
reproduces the old output: setext headings, underscore emphasis, reference links, padded tables, alternate escaping, and
more. Its knobs are grouped into themed sub-configs (``Markdown.Headings``, ``Markdown.Inline``, ``Markdown.Links``,
...) so no single object is a wall of options. The :doc:`/migration/index` guide maps each old option to its turbohtml
field.
.. testcode::
from turbohtml import Markdown
doc = turbohtml.parse('Tea
Steep it. More.
')
print(
doc.to_markdown(
Markdown(
headings=Markdown.Headings(style="setext"),
inline=Markdown.Inline(strong="__"),
links=Markdown.Links(style="reference"),
)
)
)
.. testoutput::
Tea
---
__Steep__ it. [More][1].
[1]: /x
The ``Markdown.Wrapping`` sub-config shapes the result further. ``width`` word-wraps prose at a column (``0``, the
default, leaves paragraphs unwrapped), honoring list and blockquote indentation; ``list_items`` extends wrapping into
list items and ``links=False`` keeps a ``[text](url)`` construct on one line. ``Markdown.Images(mode="html")`` and
``Markdown.Tables(mode="html")`` pass the original ``
`` or ```` through verbatim, for readers that render
embedded HTML. ``Markdown.Document(transliterate=True)`` folds common non-ASCII typography in prose (smart quotes,
dashes, ellipsis, accented letters) to ASCII:
.. testcode::
doc = turbohtml.parse("The “quick” brown fox — jumps over the lazy dog today.
")
print(doc.to_markdown(Markdown(wrapping=Markdown.Wrapping(width=30), document=Markdown.Document(transliterate=True))))
.. testoutput::
The "quick" brown fox -- jumps
over the lazy dog today.
To convert a Google Docs HTML export, use the ``Markdown.google_doc()`` preset (or set
``Markdown.GoogleDoc(enabled=True)``) so the inline-CSS styling it carries (font weight, font style, fixed-width fonts,
and ``margin-left`` list nesting) turns into Markdown:
.. testcode::
export = 'Bold and soft.
'
print(turbohtml.parse(export).to_markdown(Markdown.google_doc()))
.. testoutput::
**Bold** and *soft*.
When an option cannot express the rule you need (a custom element, or a tag that should render its own way), set the
``converters`` field: a mapping from a lowercased tag name to a ``callable(element, content) -> str``. The callable
receives the :class:`~turbohtml.Element` and the already-converted Markdown of its children, and returns the Markdown
for that element. A registered tag's built-in rendering is replaced; every other tag is untouched, and the hook costs
nothing when the mapping is omitted.
.. testcode::
html = 'Watch and MD.
'
converters = {
"video": lambda el, content: f"[{content}]({el.attrs['src']})",
"abbr": lambda el, content: f"{content} ({el.attrs['title']})",
}
print(turbohtml.parse(html).to_markdown(Markdown(converters=converters)))
.. testoutput::
Watch [a clip](/clip.mp4) and MD (Markdown).
Return ``""`` to drop an element, or return ``content`` unchanged to unwrap it. A registered block-level tag is laid out
on its own line; any other tag flows inline. The callable runs inside the same per-tree lock the walk holds, so it may
read the element's attributes and subtree freely.
To unwrap whole tags without a callable, set the ``strip`` or ``convert`` field, the two mutually exclusive filters
``markdownify`` exposes under the same names (passing both raises ``ValueError`` when the ``Markdown`` is built).
``strip`` names tags whose markup is dropped while their text keeps flowing; ``convert`` names the only tags to keep
markup for, so every other tag is unwrapped. A name the tag table does not know is ignored, and ``