/ blocks where whitespace is significant.
+ # Syntax highlighters wrap every token in (including whitespace-only
+ # spans like ) which score far below any threshold
+ # and get decomposed, corrupting code. Mirrors the scraper-side guard in
+ # WebScrapingStrategy.remove_empty_elements_fast (#1181).
+ if node.name in ("pre", "code"):
+ return
+
text_len = len(node.get_text(strip=True))
tag_len = len(node.encode_contents().decode("utf-8"))
link_text_len = sum(
diff --git a/tests/test_pruning_code_whitespace.py b/tests/test_pruning_code_whitespace.py
new file mode 100644
index 000000000..c5e8f1223
--- /dev/null
+++ b/tests/test_pruning_code_whitespace.py
@@ -0,0 +1,78 @@
+"""
+PruningContentFilter must not prune inside
/ blocks.
+
+Syntax highlighters (pygments, prism, rouge) wrap every token in ,
+including whitespace-only spans like . Those spans
+score ~0.26-0.36 against the default 0.48 threshold and were decomposed,
+collapsing `import torch` to `importtorch` — the fit_markdown counterpart of
+the scraper-side bug fixed in #1181. Heavily-highlighted
blocks (huge
+tag_len, low text density) could also be dropped wholesale.
+"""
+import pytest
+from crawl4ai.content_filter_strategy import PruningContentFilter
+
+
+# Pygments-style highlighting: every token in a span, whitespace in class="w".
+HIGHLIGHTED_CODE_HTML = """
+
+
+
Using PyTorch
+
This paragraph carries enough real prose to pass the pruning threshold
+ comfortably. It explains how to import the library and configure a device
+ before running the model, so the surrounding article content is retained
+ by the filter as fully relevant text.
Another paragraph of real prose after the code block, long enough to be
+ kept on its own merits, describing what the snippet above actually does in
+ the larger training pipeline.
+
+
+
+"""
+
+
+def _filtered_html(html: str, **kwargs) -> str:
+ chunks = PruningContentFilter(**kwargs).filter_content(html)
+ return "\n".join(chunks)
+
+
+def test_whitespace_spans_inside_code_survive():
+ out = _filtered_html(HIGHLIGHTED_CODE_HTML)
+ # The whitespace-only spans must still separate the tokens.
+ assert '' in out
+ assert "importtorch" not in out.replace("
(low text density) must be kept wholesale.
+ assert "
" in out
+ assert "triton.language" in out
+
+
+def test_code_guard_does_not_disable_normal_pruning():
+ out = _filtered_html(HIGHLIGHTED_CODE_HTML)
+ # Boilerplate outside code blocks is still pruned.
+ assert "/docs" not in out
+
+
+def test_rendered_text_keeps_token_spacing():
+ from bs4 import BeautifulSoup
+
+ out = _filtered_html(HIGHLIGHTED_CODE_HTML)
+ pre = BeautifulSoup(out, "html.parser").find("pre")
+ assert pre is not None
+ text = pre.get_text()
+ assert "import torch" in text
+ assert "FROM golang:1.21 AS builder" in text
+
+
+if __name__ == "__main__":
+ pytest.main([__file__, "-v"])