import pytest from application.parser.file.html_parser import HTMLParser HTML = ( "
Hello world.
" ) @pytest.fixture def html_file(tmp_path): path = tmp_path / "page.html" path.write_text(HTML) return path def test_html_init_parser(): parser = HTMLParser() assert isinstance(parser._init_parser(), dict) assert not parser.parser_config_set parser.init_parser() assert parser.parser_config_set def test_html_parser_extracts_text(html_file): text = HTMLParser().parse_file(html_file) assert "Heading" in text assert "Hello world." in text def test_html_parser_returns_plain_text(html_file): """bulk.py stringifies parser output, so it must not be a repr. The previous langchain BSHTMLLoader returned a Document whose ``str()`` is ``page_content='...' metadata={...}``, which leaked into indexed content. """ text = HTMLParser().parse_file(html_file) assert isinstance(text, str) assert "page_content" not in str(text) assert "metadata=" not in str(text) def test_html_parser_reports_title_metadata(html_file): assert HTMLParser().get_file_metadata(html_file) == {"title": "My Page"} def test_html_parser_metadata_without_title(tmp_path): path = tmp_path / "no_title.html" path.write_text("Just text.
") assert HTMLParser().get_file_metadata(path) == {} def test_html_parser_metadata_unreadable_file(tmp_path): assert HTMLParser().get_file_metadata(tmp_path / "missing.html") == {} # --- HTMLMarkdownParser: the anydoc engine's HTML path --------------------------- from application.parser.file.html_parser import HTMLMarkdownParser, html_to_markdown # noqa: E402 RICH_HTML = """See the ref for details.
| h1 | h2 |
|---|---|
| 1 | 2 |
typing.__name__, typing.__spec__.name
"""
@pytest.fixture
def rich_html_file(tmp_path):
path = tmp_path / "rich.html"
path.write_text(RICH_HTML)
return path
def test_markdown_parser_keeps_structure(rich_html_file):
out = HTMLMarkdownParser().parse_file(rich_html_file)
assert isinstance(out, str)
assert "# Heading" in out
assert "[the ref](../ref.html#anchor)" in out # links kept, hrefs verbatim
assert "| h1 | h2 |" in out and "| 1 | 2 |" in out # GFM table
assert "typing.__name__, typing.__spec__.name" in out # code byte-exact
def test_markdown_parser_drops_non_content(rich_html_file):
out = HTMLMarkdownParser().parse_file(rich_html_file)
assert "var secret" not in out
assert "color: red" not in out
assert "Doc Title" not in out # title is metadata, not body text
def test_markdown_parser_reports_title_metadata(rich_html_file):
assert HTMLMarkdownParser().get_file_metadata(rich_html_file) == {"title": "Doc Title"}
def test_markdown_parser_differs_from_plain_text_contract(rich_html_file):
"""``HTMLParser`` stays plain text (the ``fast`` engine's contract); only the new class emits Markdown."""
plain = HTMLParser().parse_file(rich_html_file)
assert "# Heading" not in plain
assert "[the ref]" not in plain
def test_markdown_parser_handles_xhtml(tmp_path):
path = tmp_path / "page.xhtml"
path.write_text(
'\n'
'\n'
'Body & entities.
" ) out = HTMLMarkdownParser().parse_file(path) assert "## Section" in out assert "Body & entities." in out def test_markdown_parser_init(): parser = HTMLMarkdownParser() assert not parser.parser_config_set parser.init_parser() assert parser.parser_config_set def test_html_to_markdown_collapses_blank_runs(): out = html_to_markdown("a
b
") assert "\n\n\n" not in out assert out.startswith("a") and out.endswith("b") # --- HTMLMarkdownParser: input bounding and decoding ------------------------------ def test_markdown_parser_head_truncates_oversized_markup(tmp_path, monkeypatch): """Markup past MARKUP_MAX_BYTES is not parsed: the soup+markdownify tree costs ~50x the input, and the upload cap is 100 MB.""" from application.core.settings import settings monkeypatch.setattr(settings, "MARKUP_MAX_BYTES", 600) path = tmp_path / "big.html" body = "".join(f"para {i}
\n" for i in range(300)) path.write_text("para {i}
\n" for i in range(300)) path.write_text("\n" + body + "") out = HTMLMarkdownParser().parse_file(path) assert "para 299" in out def test_markdown_parser_honours_declared_charset(tmp_path): """The file is handed to BeautifulSoup as bytes so ```` wins over the process locale (docling did the same; the text parser does not).""" path = tmp_path / "cp1250.html" path.write_bytes( 'Jiří z Plzně
".encode("cp1250") ) assert "Jiří z Plzně" in HTMLMarkdownParser().parse_file(path) assert HTMLMarkdownParser().get_file_metadata(path) == {"title": "Plzeň"} def test_markdown_parser_metadata_unreadable_file(tmp_path): assert HTMLMarkdownParser().get_file_metadata(tmp_path / "missing.html") == {} def test_markdown_parser_cut_inside_utf8_char_does_not_mojibake_the_page(tmp_path, monkeypatch): """A byte cut mid-character makes strict UTF-8 fail; BeautifulSoup would then retry as windows-1252, which *succeeds* on umlauts and garbles everything.""" from application.core.settings import settings path = tmp_path / "minified.html" body = "Größe Übermaß schön für
" * 400 # no newlines: the cut is arbitrary path.write_bytes( ('para
" ) text = HTMLMarkdownParser().parse_file(path) assert "xml version" not in text assert "raw" not in text assert text.startswith("# Hi") def test_data_uris_are_stripped_from_images_and_links(): payload = "data:image/png;base64," + "A" * 200_000 html = ( f'before
after
' ) text = html_to_markdown(html) assert "AAAA" not in text assert "before" in text and "after" in text assert len(text) < 200 def test_utf16_head_keeps_an_even_byte_count(tmp_path, monkeypatch): from application.core.settings import settings from application.parser.file.html_parser import HTMLMarkdownParser, read_markup_head body = "".join(f"Zeile {i} Über Größe
\n" for i in range(200)) path = tmp_path / "wide.html" path.write_bytes(("" + body + "").encode("utf-16")) # BOM-prefixed head = read_markup_head(path, 3001) assert len(head) % 2 == 0 monkeypatch.setattr(settings, "MARKUP_MAX_BYTES", 3001) text = HTMLMarkdownParser().parse_file(path) assert "Zeile 0 Über Größe" in text assert "\x00" not in text