69 lines
2.0 KiB
Python
69 lines
2.0 KiB
Python
"""把文章正文 HTML 片段转换为 Markdown。
|
||
|
||
文章正文是若干 ``<p>`` 段落,内部可能含 ``<br>``、``<strong>``、``<a>``、
|
||
``<img>`` 等。本模块递归地把行内节点转成 Markdown,``<img>`` 通过
|
||
``resolve_image`` 回调决定最终链接(本地化或远程)。
|
||
"""
|
||
|
||
import re
|
||
from urllib.parse import urljoin
|
||
|
||
from bs4 import NavigableString, Tag
|
||
|
||
from config import BASE_URL
|
||
|
||
_WS_RE = re.compile(r"\s+")
|
||
|
||
|
||
def _abs(href: str) -> str:
|
||
"""把相对地址补成绝对 URL。"""
|
||
if not href:
|
||
return ""
|
||
return urljoin(BASE_URL + "/", href)
|
||
|
||
|
||
def _inline(node, resolve_image) -> str:
|
||
"""递归转换单个节点为 Markdown 文本。"""
|
||
if isinstance(node, NavigableString):
|
||
return str(node)
|
||
if not isinstance(node, Tag):
|
||
return ""
|
||
|
||
name = node.name
|
||
if name == "br":
|
||
return "\n"
|
||
|
||
inner = "".join(_inline(c, resolve_image) for c in node.children)
|
||
|
||
if name in ("strong", "b"):
|
||
s = inner.strip()
|
||
return f"**{s}**" if s else ""
|
||
if name in ("em", "i"):
|
||
s = inner.strip()
|
||
return f"*{s}*" if s else ""
|
||
if name == "a":
|
||
text = inner.strip()
|
||
href = _abs(node.get("href", ""))
|
||
return f"[{text}]({href})" if text else ""
|
||
if name == "img":
|
||
src = _abs(node.get("src", ""))
|
||
alt = (node.get("alt") or "").strip()
|
||
return resolve_image(src, alt)
|
||
|
||
# span / div / font / u 等容器:透传内容
|
||
return inner
|
||
|
||
|
||
def blocks_to_markdown(elements, resolve_image) -> str:
|
||
"""把文章正文的块级元素(``<p>`` 与块级 ``<img>`` 等,按文档顺序)转为 Markdown。
|
||
|
||
源页面的图表是 ``container-narrow`` 的直接子 ``<img>``,与 ``<p>`` 交替出现,
|
||
因此必须按顺序遍历而非只取 ``<p>``。块内空白折叠为单空格,块间用空行分隔。
|
||
"""
|
||
blocks: list[str] = []
|
||
for el in elements:
|
||
text = _WS_RE.sub(" ", _inline(el, resolve_image)).strip()
|
||
if text:
|
||
blocks.append(text)
|
||
return "\n\n".join(blocks)
|