Python 76.2%
JavaScript 11.3%
CSS 6.3%
HTML 5.9%
1from trawls.processors.html_to_md import html_to_markdown23ARTICLE = """4<html lang="fr"><head><title>Titre</title><style>.x{}</style><script>var a=1;</script></head>5<body>6<nav><a href="/">Accueil</a><a href="/blog">Blog</a><a href="/contact">Contact</a><a href="/a">A</a></nav>7<div class="cookie-banner">Nous utilisons des cookies. <a href="#">Accepter</a></div>8<main>9<article>10<h1>Le chalut de fond</h1>11<p>Le <strong>chalut</strong> est un filet <em>remorqué</em> par un navire. Il ratisse le fond et remonte tout ce qui s'y trouve, ce qui en fait un engin très efficace mais controversé.</p>12<h2>Fonctionnement</h2>13<p>Le filet est maintenu ouvert par des panneaux. Voir <a href="/details">les détails</a>.</p>14<ul><li>Panneaux divergents</li><li>Bourrelet<ul><li>Diabolos</li></ul></li></ul>15<table><tr><th>Type</th><th>Profondeur</th></tr><tr><td>Fond</td><td>200 m</td></tr><tr><td rowspan="2">Pélagique</td><td>50 m</td></tr><tr><td>100 m</td></tr></table>16<pre><code class="language-python">print("ok")</code></pre>17<img src="/img/chalut.png" alt="Un chalut">18<img src="data:image/png;base64,AAAA" alt="b64">19</article>20</main>21<aside class="sidebar"><a href="/1">Lien 1</a><a href="/2">Lien 2</a><a href="/3">Lien 3</a><a href="/4">Lien 4</a></aside>22<footer>© 2026 Exemple — <a href="/mentions">Mentions légales</a></footer>23</body></html>24"""252627def test_main_content_and_boilerplate() -> None:28 r = html_to_markdown(ARTICLE, "https://ex.com/a/b")29 md = r.markdown30 assert md.startswith("# Le chalut de fond")31 assert "**chalut**" in md and "*remorqué*" in md32 assert "## Fonctionnement" in md33 assert "[les détails](https://ex.com/details)" in md34 assert "- Panneaux divergents" in md and " - Diabolos" in md35 assert "| Type | Profondeur |" in md and "| Pélagique | 100 m |" in md36 assert '```python\nprint("ok")\n```' in md37 assert "" in md38 assert "b64" not in md39 for bad in ("Accueil", "cookies", "Mentions légales", "Lien 1"):40 assert bad not in md, bad414243def test_full_page_keeps_nav_text() -> None:44 r = html_to_markdown(ARTICLE, "https://ex.com", only_main_content=False)45 assert "Accueil" in r.markdown and "Le chalut de fond" in r.markdown464748def test_broken_html_never_raises() -> None:49 for html in (50 "",51 "<div><p>ouvert",52 "<<<>>>",53 "<html><body><table><tr><td>x</table>",54 "\x00\x01garbage",55 "<p>" * 5000,56 ):57 r = html_to_markdown(html, "https://ex.com")58 assert isinstance(r.markdown, str)596061def test_citations() -> None:62 r = html_to_markdown(63 "<article><p>Voir <a href='https://a.com'>A</a> et <a href='https://b.com'>B</a> puis <a href='https://a.com'>A encore</a>. Un paragraphe assez long pour que le scoring le garde comme contenu principal, avec plusieurs phrases, des virgules, et du texte.</p></article>",64 "https://ex.com",65 citations=True,66 )67 assert "[A][1]" in r.markdown and "[B][2]" in r.markdown and "[A encore][1]" in r.markdown68 assert "[1]: https://a.com" in r.markdown697071def test_headings_normalized() -> None:72 r = html_to_markdown(73 "<body><h1>Un</h1><p>Texte assez long pour compter comme du contenu, avec des phrases et de la ponctuation. Encore une phrase ici.</p><h1>Deux</h1><p>Suite du texte, encore quelques mots pour la densité, et une virgule.</p></body>",74 "https://ex.com",75 only_main_content=False,76 )77 assert r.markdown.count("\n# ") + (1 if r.markdown.startswith("# ") else 0) == 178 assert "## Deux" in r.markdown798081def test_definition_list_and_details() -> None:82 r = html_to_markdown(83 "<body><dl><dt>Terme</dt><dd>Définition</dd></dl><details><summary>Plus</summary><p>Caché</p></details></body>",84 "https://ex.com",85 only_main_content=False,86 )87 assert (88 "**Terme**" in r.markdown89 and ": Définition" in r.markdown90 and "**Plus**" in r.markdown91 and "Caché" in r.markdown92 )93