把 markdown 切分为块列表。 - heading / hr / code 各自单独成块 - 连续 text 行(无空行间隔)合成一个块;进一步根据首行特征分类为 paragraph / list / blockquote / table / figure - blank 行只作分隔符,不进入块
(text: str)
| 105 | |
| 106 | |
| 107 | def split_blocks(text: str) -> list[Block]: |
| 108 | """ |
| 109 | 把 markdown 切分为块列表。 |
| 110 | - heading / hr / code 各自单独成块 |
| 111 | - 连续 text 行(无空行间隔)合成一个块;进一步根据首行特征分类为 paragraph / list / blockquote / table / figure |
| 112 | - blank 行只作分隔符,不进入块 |
| 113 | """ |
| 114 | if not text: |
| 115 | return [] |
| 116 | lines = text.split("\n") |
| 117 | n = len(lines) |
| 118 | starts = _line_starts(text) |
| 119 | classes = _classify_lines(lines) |
| 120 | text_len = len(text) |
| 121 | |
| 122 | def line_char_end(idx: int) -> int: |
| 123 | # idx 行末字符偏移(不含换行符) |
| 124 | if idx < n - 1: |
| 125 | return starts[idx + 1] - 1 |
| 126 | return text_len |
| 127 | |
| 128 | blocks: list[Block] = [] |
| 129 | i = 0 |
| 130 | while i < n: |
| 131 | c = classes[i] |
| 132 | if c == "blank": |
| 133 | i += 1 |
| 134 | continue |
| 135 | if c == "heading": |
| 136 | line = lines[i] |
| 137 | m = HEADING_RE.match(line) |
| 138 | level = len(m.group(1)) |
| 139 | title = m.group(2).strip() |
| 140 | # 标题文本可能已经带 anchor(重复处理时),剥离 |
| 141 | title = ANCHOR_TAIL_RE.sub("", title).strip() |
| 142 | blocks.append(Block( |
| 143 | kind="heading", |
| 144 | text=line, |
| 145 | line_start=i + 1, |
| 146 | line_end=i + 1, |
| 147 | char_start=starts[i], |
| 148 | char_end=line_char_end(i), |
| 149 | level=level, |
| 150 | title=title, |
| 151 | )) |
| 152 | i += 1 |
| 153 | continue |
| 154 | if c == "hr": |
| 155 | line = lines[i] |
| 156 | blocks.append(Block( |
| 157 | kind="hr", |
| 158 | text=line, |
| 159 | line_start=i + 1, |
| 160 | line_end=i + 1, |
| 161 | char_start=starts[i], |
| 162 | char_end=line_char_end(i), |
| 163 | )) |
| 164 | i += 1 |