From 6fd237986ece44915918d7f6fa50edb1a93fa922 Mon Sep 17 00:00:00 2001 From: BukeLy Date: Tue, 28 Apr 2026 15:23:34 +0800 Subject: [PATCH 1/2] Recognize whole-line bold as level-1 heading in markdown parser MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit extract_nodes_from_markdown now matches `**Title**` lines as level-1 headings (alongside ATX `#` headings) and attaches the heading level on the producer side. extract_node_text_content reads the level from the node instead of re-running a `^#{1,6}` regex on the source line, which was silently dropping bold-heading nodes from OCR / MinerU output. Bold maps to level 1 even when mixed with `#` / `##` / `###` — bold-as- heading is a courtesy heuristic for non-ATX markdown sources, and CommonMark has no concept of bold heading depth. --- pageindex/page_index_md.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/pageindex/page_index_md.py b/pageindex/page_index_md.py index 5a59716..7785f81 100644 --- a/pageindex/page_index_md.py +++ b/pageindex/page_index_md.py @@ -31,6 +31,7 @@ async def generate_summaries_for_structure_md(structure, summary_token_threshold def extract_nodes_from_markdown(markdown_content): header_pattern = r'^(#{1,6})\s+(.+)$' + bold_heading_pattern = r'^\*\*(.+?)\*\*\s*$' code_block_pattern = r'^```' node_list = [] @@ -54,7 +55,14 @@ def extract_nodes_from_markdown(markdown_content): match = re.match(header_pattern, stripped_line) if match: title = match.group(2).strip() - node_list.append({'node_title': title, 'line_num': line_num}) + level = len(match.group(1)) + node_list.append({'node_title': title, 'line_num': line_num, 'level': level}) + continue + + bold_match = re.match(bold_heading_pattern, stripped_line) + if bold_match: + title = bold_match.group(1).strip() + node_list.append({'node_title': title, 'line_num': line_num, 'level': 1}) return node_list, lines @@ -62,17 +70,10 @@ def extract_nodes_from_markdown(markdown_content): def extract_node_text_content(node_list, markdown_lines): all_nodes = [] for node in node_list: - line_content = markdown_lines[node['line_num'] - 1] - header_match = re.match(r'^(#{1,6})', line_content) - - if header_match is None: - print(f"Warning: Line {node['line_num']} does not contain a valid header: '{line_content}'") - continue - processed_node = { 'title': node['node_title'], 'line_num': node['line_num'], - 'level': len(header_match.group(1)) + 'level': node['level'] } all_nodes.append(processed_node) From 01dbcbf47f50030d1df0aa1adec3d417c6328977 Mon Sep 17 00:00:00 2001 From: BukeLy Date: Thu, 16 Jul 2026 19:36:52 +0800 Subject: [PATCH 2/2] fix(markdown): skip empty bold headings --- pageindex/page_index_md.py | 5 +++-- tests/test_page_index_md.py | 23 +++++++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 tests/test_page_index_md.py diff --git a/pageindex/page_index_md.py b/pageindex/page_index_md.py index 7785f81..7c5e958 100644 --- a/pageindex/page_index_md.py +++ b/pageindex/page_index_md.py @@ -62,7 +62,8 @@ def extract_nodes_from_markdown(markdown_content): bold_match = re.match(bold_heading_pattern, stripped_line) if bold_match: title = bold_match.group(1).strip() - node_list.append({'node_title': title, 'line_num': line_num, 'level': 1}) + if title: + node_list.append({'node_title': title, 'line_num': line_num, 'level': 1}) return node_list, lines @@ -340,4 +341,4 @@ if __name__ == "__main__": with open(output_path, 'w', encoding='utf-8') as f: json.dump(tree_structure, f, indent=2, ensure_ascii=False) - print(f"\nTree structure saved to: {output_path}") \ No newline at end of file + print(f"\nTree structure saved to: {output_path}") diff --git a/tests/test_page_index_md.py b/tests/test_page_index_md.py new file mode 100644 index 0000000..12aa908 --- /dev/null +++ b/tests/test_page_index_md.py @@ -0,0 +1,23 @@ +import unittest + +from pageindex.page_index_md import extract_nodes_from_markdown + + +class ExtractNodesFromMarkdownTest(unittest.TestCase): + def test_skips_bold_heading_with_only_whitespace(self): + nodes, _ = extract_nodes_from_markdown("** **\n**Valid heading**") + + self.assertEqual( + nodes, + [ + { + "node_title": "Valid heading", + "line_num": 2, + "level": 1, + } + ], + ) + + +if __name__ == "__main__": + unittest.main()