From df580c75dfda66313a10ae2914404f6a133e7129 Mon Sep 17 00:00:00 2001 From: Adarsh-mk7 Date: Sat, 10 Oct 2026 16:32:22 +0530 Subject: [PATCH 01/11] test: add regression coverage for consecutive partial numberings --- packages/markitdown/tests/test_pdf.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/packages/markitdown/tests/test_pdf.py b/packages/markitdown/tests/test_pdf.py index c4f8c64a9c..9442decf09 100644 --- a/packages/markitdown/tests/test_pdf.py +++ b/packages/markitdown/tests/test_pdf.py @@ -1195,6 +1195,22 @@ def test_multiple_partial_numberings_all_merged(self): isolated_count == 0 ), f"Found {isolated_count} isolated partial numberings that weren't merged" + def test_merge_partial_numbering_consecutive_standalone(self): + """Test that two consecutive partial numberings are not merged together into a single token.""" + text = ( + ".1\n" + ".2\n" + "Contractor shall furnish all materials.\n" + ".3\n" + "Work shall comply with local codes." + ) + expected = ( + ".1\n" + ".2 Contractor shall furnish all materials.\n" + ".3 Work shall comply with local codes." + ) + assert _merge_partial_numbering_lines(text) == expected + # Page cleanup and extraction fallback From ab65d21d44d83390e73115a0155f6438aa6886b9 Mon Sep 17 00:00:00 2001 From: Adarsh-mk7 Date: Sat, 10 Oct 2026 16:33:07 +0530 Subject: [PATCH 02/11] test: cover multiple consecutive numbers and trailing standalone numbers --- packages/markitdown/tests/test_pdf.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/packages/markitdown/tests/test_pdf.py b/packages/markitdown/tests/test_pdf.py index 9442decf09..0423dbbcda 100644 --- a/packages/markitdown/tests/test_pdf.py +++ b/packages/markitdown/tests/test_pdf.py @@ -1211,6 +1211,25 @@ def test_merge_partial_numbering_consecutive_standalone(self): ) assert _merge_partial_numbering_lines(text) == expected + def test_merge_partial_numbering_multiple_consecutive_and_trailing(self): + """Test three or more consecutive partial numberings and trailing standalone numbers.""" + three_consecutive = ( + ".1\n" + ".2\n" + ".3\n" + "Final requirements and specification details." + ) + expected_three = ( + ".1\n" + ".2\n" + ".3 Final requirements and specification details." + ) + assert _merge_partial_numbering_lines(three_consecutive) == expected_three + + trailing_numbers = ".1\n.2\n.3" + expected_trailing = ".1\n.2\n.3" + assert _merge_partial_numbering_lines(trailing_numbers) == expected_trailing + # Page cleanup and extraction fallback From 14226b2caf0e0d9cd2b6868385889a0bd76e3d77 Mon Sep 17 00:00:00 2001 From: Adarsh-mk7 Date: Sat, 10 Oct 2026 16:33:42 +0530 Subject: [PATCH 03/11] test: cover consecutive partial numberings separated by blank lines and whitespace --- packages/markitdown/tests/test_pdf.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/packages/markitdown/tests/test_pdf.py b/packages/markitdown/tests/test_pdf.py index 0423dbbcda..731cf08c6c 100644 --- a/packages/markitdown/tests/test_pdf.py +++ b/packages/markitdown/tests/test_pdf.py @@ -1230,6 +1230,24 @@ def test_merge_partial_numbering_multiple_consecutive_and_trailing(self): expected_trailing = ".1\n.2\n.3" assert _merge_partial_numbering_lines(trailing_numbers) == expected_trailing + @pytest.mark.parametrize( + "separator", + ["\n", "\n\n", "\n \n\t\n"], + ids=["single-blank-line", "multiple-blank-lines", "whitespace-lines"], + ) + def test_merge_partial_numbering_consecutive_with_blank_lines(self, separator): + """Test consecutive partial numbers separated by blank lines preserve their structure.""" + text = ( + f".1{separator}" + ".2\n" + "Contractor shall furnish all materials." + ) + expected = ( + f".1{separator}" + ".2 Contractor shall furnish all materials." + ) + assert _merge_partial_numbering_lines(text) == expected + # Page cleanup and extraction fallback From 03bafdbeb798295160858e77b7e703ce1674e2a0 Mon Sep 17 00:00:00 2001 From: Adarsh-mk7 Date: Sat, 10 Oct 2026 16:34:16 +0530 Subject: [PATCH 04/11] test: cover standalone partial numbering preceding inline numbered lines --- packages/markitdown/tests/test_pdf.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/packages/markitdown/tests/test_pdf.py b/packages/markitdown/tests/test_pdf.py index 731cf08c6c..6b7d8865bf 100644 --- a/packages/markitdown/tests/test_pdf.py +++ b/packages/markitdown/tests/test_pdf.py @@ -1248,6 +1248,20 @@ def test_merge_partial_numbering_consecutive_with_blank_lines(self, separator): ) assert _merge_partial_numbering_lines(text) == expected + def test_merge_partial_numbering_preceding_inline_numbered_line(self): + """Test standalone partial number does not merge into a line already prefixed with a partial number.""" + text = ( + ".1\n" + ".2 Contractor shall furnish all materials.\n" + ".3 Work shall comply with local codes." + ) + expected = ( + ".1\n" + ".2 Contractor shall furnish all materials.\n" + ".3 Work shall comply with local codes." + ) + assert _merge_partial_numbering_lines(text) == expected + # Page cleanup and extraction fallback From 2646ee26a156aad475c5113791b2e0ce57cb6d34 Mon Sep 17 00:00:00 2001 From: Adarsh-mk7 Date: Sat, 10 Oct 2026 16:35:16 +0530 Subject: [PATCH 05/11] fix: prevent merging partial numbering into consecutive standalone number --- .../markitdown/src/markitdown/converters/_pdf_converter.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/markitdown/src/markitdown/converters/_pdf_converter.py b/packages/markitdown/src/markitdown/converters/_pdf_converter.py index ffbcbd990c..f6e4638552 100644 --- a/packages/markitdown/src/markitdown/converters/_pdf_converter.py +++ b/packages/markitdown/src/markitdown/converters/_pdf_converter.py @@ -41,13 +41,13 @@ def _merge_partial_numbering_lines(text: str) -> str: while j < len(lines) and not lines[j].strip(): j += 1 - if j < len(lines): + if j < len(lines) and not PARTIAL_NUMBERING_PATTERN.match(lines[j].strip()): # Merge the partial numbering with the next line next_line = lines[j].strip() result_lines.append(f"{stripped} {next_line}") i = j + 1 # Skip past the merged line else: - # No next line to merge with, keep as is + # Next line is also a partial numbering, or no next line to merge with result_lines.append(line) i += 1 else: From f1adf737cf707ed843b127fcb39dbde5c662646b Mon Sep 17 00:00:00 2001 From: Adarsh-mk7 Date: Sat, 10 Oct 2026 16:35:46 +0530 Subject: [PATCH 06/11] test: verify core fix with parameterized digit variations --- packages/markitdown/tests/test_pdf.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/packages/markitdown/tests/test_pdf.py b/packages/markitdown/tests/test_pdf.py index 6b7d8865bf..9bd908ea90 100644 --- a/packages/markitdown/tests/test_pdf.py +++ b/packages/markitdown/tests/test_pdf.py @@ -1262,6 +1262,29 @@ def test_merge_partial_numbering_preceding_inline_numbered_line(self): ) assert _merge_partial_numbering_lines(text) == expected + @pytest.mark.parametrize( + "first_num,second_num", + [ + (".1", ".2"), + (".9", ".10"), + (".10", ".11"), + (".99", ".100"), + (".1", ".100"), + ], + ) + def test_merge_partial_numbering_varying_digit_lengths(self, first_num, second_num): + """Test consecutive partial numbering protection across varying digit lengths.""" + text = ( + f"{first_num}\n" + f"{second_num}\n" + "Technical specification content." + ) + expected = ( + f"{first_num}\n" + f"{second_num} Technical specification content." + ) + assert _merge_partial_numbering_lines(text) == expected + # Page cleanup and extraction fallback From 156af987cfcfebf1071315273dc5239530259307 Mon Sep 17 00:00:00 2001 From: Adarsh-mk7 Date: Sat, 10 Oct 2026 16:36:35 +0530 Subject: [PATCH 07/11] fix: prevent merging partial numbering into lines starting with partial numbering --- .../markitdown/src/markitdown/converters/_pdf_converter.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/markitdown/src/markitdown/converters/_pdf_converter.py b/packages/markitdown/src/markitdown/converters/_pdf_converter.py index f6e4638552..f31a08fc06 100644 --- a/packages/markitdown/src/markitdown/converters/_pdf_converter.py +++ b/packages/markitdown/src/markitdown/converters/_pdf_converter.py @@ -9,6 +9,7 @@ # Pattern for MasterFormat-style partial numbering (e.g., ".1", ".2", ".10") PARTIAL_NUMBERING_PATTERN = re.compile(r"^\.\d+$") +PARTIAL_NUMBERING_PREFIX_PATTERN = re.compile(r"^\.\d+(\s+|$)") def _merge_partial_numbering_lines(text: str) -> str: @@ -41,7 +42,7 @@ def _merge_partial_numbering_lines(text: str) -> str: while j < len(lines) and not lines[j].strip(): j += 1 - if j < len(lines) and not PARTIAL_NUMBERING_PATTERN.match(lines[j].strip()): + if j < len(lines) and not PARTIAL_NUMBERING_PREFIX_PATTERN.match(lines[j].strip()): # Merge the partial numbering with the next line next_line = lines[j].strip() result_lines.append(f"{stripped} {next_line}") From 98c3e89e1d936a1079614662506fb03c9b76e949 Mon Sep 17 00:00:00 2001 From: Adarsh-mk7 Date: Sat, 10 Oct 2026 16:37:08 +0530 Subject: [PATCH 08/11] test: verify preservation of blank lines between consecutive partial numberings --- packages/markitdown/tests/test_pdf.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/packages/markitdown/tests/test_pdf.py b/packages/markitdown/tests/test_pdf.py index 9bd908ea90..455846faf6 100644 --- a/packages/markitdown/tests/test_pdf.py +++ b/packages/markitdown/tests/test_pdf.py @@ -1285,6 +1285,27 @@ def test_merge_partial_numbering_varying_digit_lengths(self, first_num, second_n ) assert _merge_partial_numbering_lines(text) == expected + def test_merge_partial_numbering_preserves_multiple_blank_lines_between_sections(self): + """Test multiple blank lines between sections with consecutive numbers are preserved.""" + text = ( + "Section 01 00 00\n\n\n" + ".1\n\n" + "Preliminary note.\n\n\n" + ".2\n\n" + ".3\n\n" + "Execution requirements.\n\n\n" + ".4\n\n" + "Quality assurance." + ) + expected = ( + "Section 01 00 00\n\n\n" + ".1 Preliminary note.\n\n\n" + ".2\n\n" + ".3 Execution requirements.\n\n\n" + ".4 Quality assurance." + ) + assert _merge_partial_numbering_lines(text) == expected + # Page cleanup and extraction fallback From 4cb0b15d58875c61a0635a9d177b64184ca51003 Mon Sep 17 00:00:00 2001 From: Adarsh-mk7 Date: Sat, 10 Oct 2026 16:38:14 +0530 Subject: [PATCH 09/11] test: add end-to-end PDF conversion test for consecutive partial numberings --- packages/markitdown/tests/test_pdf.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/packages/markitdown/tests/test_pdf.py b/packages/markitdown/tests/test_pdf.py index 455846faf6..041c2c6415 100644 --- a/packages/markitdown/tests/test_pdf.py +++ b/packages/markitdown/tests/test_pdf.py @@ -1306,6 +1306,23 @@ def test_merge_partial_numbering_preserves_multiple_blank_lines_between_sections ) assert _merge_partial_numbering_lines(text) == expected + def test_end_to_end_pdf_conversion_with_consecutive_partial_numbering(self): + """Test full MarkItDown PDF conversion pipeline preserves consecutive partial numbering.""" + pdf_path = os.path.join(TEST_FILES_DIR, "test.pdf") + simulated_pdfminer_text = ( + ".1\n" + ".2\n" + "Contractor shall furnish all materials.\n" + ".3\n" + "Work shall comply with local codes." + ) + with patch("pdfminer.high_level.extract_text", return_value=simulated_pdfminer_text): + result = MarkItDown().convert(pdf_path) + + assert ".1 .2" not in result.text_content + assert ".1\n.2 Contractor shall furnish all materials." in result.text_content + assert ".3 Work shall comply with local codes." in result.text_content + # Page cleanup and extraction fallback From e88fe9ef497e6b7e99af50ccb839a0b507593a69 Mon Sep 17 00:00:00 2001 From: Adarsh-mk7 Date: Sat, 10 Oct 2026 16:38:52 +0530 Subject: [PATCH 10/11] docs: document consecutive numbering handling and invariants in _pdf_converter.py --- .../src/markitdown/converters/_pdf_converter.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/markitdown/src/markitdown/converters/_pdf_converter.py b/packages/markitdown/src/markitdown/converters/_pdf_converter.py index f31a08fc06..4181fe1688 100644 --- a/packages/markitdown/src/markitdown/converters/_pdf_converter.py +++ b/packages/markitdown/src/markitdown/converters/_pdf_converter.py @@ -25,7 +25,10 @@ def _merge_partial_numbering_lines(text: str) -> str: .1 The intent of this Request for Proposal... - This function merges them back together. + This function merges them back together. When consecutive partial numberings + appear (e.g., an unnumbered section stub or consecutive sub-items like + '.1\n.2\nContractor shall...'), standalone numbers are preserved on their own + lines rather than being collapsed into an invalid compound token like '.1 .2'. """ lines = text.split("\n") result_lines: list[str] = [] @@ -42,13 +45,15 @@ def _merge_partial_numbering_lines(text: str) -> str: while j < len(lines) and not lines[j].strip(): j += 1 + # Only merge if the next line is actual content, not another partial numbering if j < len(lines) and not PARTIAL_NUMBERING_PREFIX_PATTERN.match(lines[j].strip()): # Merge the partial numbering with the next line next_line = lines[j].strip() result_lines.append(f"{stripped} {next_line}") i = j + 1 # Skip past the merged line else: - # Next line is also a partial numbering, or no next line to merge with + # Next line is also a partial numbering, or no next line to merge with; + # preserve the standalone partial numbering as-is. result_lines.append(line) i += 1 else: From bc8554dc207176f93ad06d1676be0b137c3d5cf0 Mon Sep 17 00:00:00 2001 From: Adarsh-mk7 Date: Sat, 10 Oct 2026 16:39:28 +0530 Subject: [PATCH 11/11] test: strengthen boundary condition coverage for decimal and text edge cases --- packages/markitdown/tests/test_pdf.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/packages/markitdown/tests/test_pdf.py b/packages/markitdown/tests/test_pdf.py index 041c2c6415..b780f4c766 100644 --- a/packages/markitdown/tests/test_pdf.py +++ b/packages/markitdown/tests/test_pdf.py @@ -1323,6 +1323,30 @@ def test_end_to_end_pdf_conversion_with_consecutive_partial_numbering(self): assert ".1\n.2 Contractor shall furnish all materials." in result.text_content assert ".3 Work shall comply with local codes." in result.text_content + @pytest.mark.parametrize( + "non_partial_pattern", + [ + ".5 mg", + "1.2", + ".a", + "..1", + "section .1", + "... ellipsis", + ], + ids=["unit-suffix", "standard-decimal", "letter-suffix", "double-dot", "text-prefix", "ellipsis"], + ) + def test_merge_partial_numbering_boundary_conditions(self, non_partial_pattern): + """Test boundary conditions ensure non-partial numbering lines are unaffected.""" + text = ( + f"{non_partial_pattern}\n" + "Following line of text." + ) + expected = ( + f"{non_partial_pattern}\n" + "Following line of text." + ) + assert _merge_partial_numbering_lines(text) == expected + # Page cleanup and extraction fallback