Skip to content

Commit 440f4fc

Browse files
Otis0408Otis0408
authored andcommitted
fix: MarkdownHeaderSplitter dropped content before an embedded header
With keep_headers=False the secondary split re-stripped a leading header using a non-anchored re.search, which matched the first '#' line anywhere in the body (an embedded lower-level header, or a '#' comment inside a fenced code block) and discarded everything before it. Anchor the match to the start of the content with re.match so only a genuine leading header is stripped.
1 parent 9a01927 commit 440f4fc

3 files changed

Lines changed: 33 additions & 1 deletion

File tree

haystack/components/preprocessors/markdown_header_splitter.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,7 @@ def _apply_secondary_splitting(self, documents: list[Document]) -> list[Document
223223

224224
if not self.keep_headers: # skip header extraction if keep_headers
225225
# extract header information
226-
header_match = re.search(self._header_pattern, doc.content)
226+
header_match = re.match(self._header_pattern, doc.content)
227227
if header_match:
228228
content_for_splitting = doc.content[header_match.end() :]
229229

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
fixes:
3+
- |
4+
Fixed ``MarkdownHeaderSplitter`` dropping content when ``keep_headers=False``.
5+
The secondary split re-stripped a leading header with a non-anchored search,
6+
which matched the first ``#`` line anywhere in the body (an embedded
7+
lower-level header, or a ``#`` comment inside a fenced code block) and
8+
discarded everything before it. The match is now anchored to the start of the
9+
content, so only a genuine leading header is stripped.

test/components/preprocessors/test_markdown_header_splitter.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,29 @@ def test_preserve_document_metadata():
250250
assert split_docs[0].content == "\nContent"
251251

252252

253+
def test_secondary_split_keeps_content_before_embedded_header():
254+
"""With keep_headers=False, prose before an embedded lower-level header must
255+
not be dropped during the secondary split."""
256+
splitter = MarkdownHeaderSplitter(
257+
keep_headers=False, header_split_levels=[1], secondary_split="word", split_length=100
258+
)
259+
docs = splitter.run(documents=[Document(content="# Main\nintro paragraph text\n## Sub\nmore text\n")])["documents"]
260+
combined = "".join(doc.content or "" for doc in docs)
261+
assert "intro paragraph text" in combined
262+
263+
264+
def test_secondary_split_keeps_content_before_code_fence_comment():
265+
"""With keep_headers=False, prose before a '#' comment inside a fenced code block must
266+
not be dropped during the secondary split (the comment is not a real header)."""
267+
splitter = MarkdownHeaderSplitter(
268+
keep_headers=False, header_split_levels=[1], secondary_split="word", split_length=100
269+
)
270+
content = "# Main\nsome intro text\n```python\n# a comment in code\n```\nmore text\n"
271+
docs = splitter.run(documents=[Document(content=content)])["documents"]
272+
combined = "".join(doc.content or "" for doc in docs)
273+
assert "some intro text" in combined
274+
275+
253276
# Error and edge case handling
254277
def test_non_text_document():
255278
"""Test that the component correctly handles non-text documents."""

0 commit comments

Comments
 (0)