- CTNode: 트리 기본 단위 (전위/후위/BFS 순회, 탐색, 분리) - CTTree: 트리 컨테이너 (빌더 API, TOC, filter/map/merge) - NodeType: 구조/블록/인라인/메타 노드 타입 Enum - MarkdownParser: Markdown → CTTree 파서 - MarkdownRenderer: CTTree → Markdown 렌더러 - JSONRenderer: CTTree → JSON 직렬화 - 27개 단위/통합 테스트 전체 통과
89 lines
2.5 KiB
Python
89 lines
2.5 KiB
Python
"""
|
|
ctstruct/renderers/markdown.py
|
|
CTTree → Markdown 렌더러
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from ..node import CTNode
|
|
from ..tree import CTTree
|
|
from ..types import NodeType
|
|
from .base import BaseRenderer
|
|
|
|
|
|
class MarkdownRenderer(BaseRenderer):
|
|
"""CTTree 를 Markdown 텍스트로 렌더링."""
|
|
|
|
def render(self, tree: CTTree) -> str:
|
|
lines: list[str] = []
|
|
# frontmatter
|
|
meta = tree.root.attr("metadata")
|
|
if meta:
|
|
lines.append("---")
|
|
for k, v in meta.items():
|
|
lines.append(f"{k}: {v}")
|
|
lines.append("---")
|
|
lines.append("")
|
|
|
|
for child in tree.root:
|
|
lines.extend(self._render_node(child))
|
|
lines.append("")
|
|
|
|
return "\n".join(lines).rstrip() + "\n"
|
|
|
|
def _render_node(self, node: CTNode) -> list[str]:
|
|
t = node.node_type
|
|
|
|
if t == NodeType.SECTION:
|
|
out: list[str] = []
|
|
for c in node:
|
|
out.extend(self._render_node(c))
|
|
out.append("")
|
|
return out
|
|
|
|
if t == NodeType.HEADING:
|
|
level = node.attr("level", 1)
|
|
return [f"{'#' * level} {node.content or ''}"]
|
|
|
|
if t == NodeType.PARAGRAPH:
|
|
return [node.content or ""]
|
|
|
|
if t == NodeType.CODE_BLOCK:
|
|
lang = node.attr("language", "")
|
|
return [f"```{lang}", node.content or "", "```"]
|
|
|
|
if t == NodeType.BLOCKQUOTE:
|
|
return [f"> {line}" for line in (node.content or "").splitlines()]
|
|
|
|
if t == NodeType.DIVIDER:
|
|
return ["---"]
|
|
|
|
if t == NodeType.LIST:
|
|
out = []
|
|
style = node.attr("style", "unordered")
|
|
for idx, item in enumerate(node, 1):
|
|
prefix = f"{idx}." if style == "ordered" else "-"
|
|
out.append(f"{prefix} {item.content or ''}")
|
|
return out
|
|
|
|
if t == NodeType.TABLE:
|
|
return self._render_table(node)
|
|
|
|
if t == NodeType.METADATA:
|
|
return [] # 메타 노드는 frontmatter로 처리됨
|
|
|
|
# 그 외 노드는 텍스트 추출
|
|
return [node.text_content()]
|
|
|
|
def _render_table(self, table: CTNode) -> list[str]:
|
|
rows = list(table)
|
|
if not rows:
|
|
return []
|
|
out: list[str] = []
|
|
for i, row in enumerate(rows):
|
|
cells = [c.content or "" for c in row]
|
|
out.append("| " + " | ".join(cells) + " |")
|
|
if i == 0:
|
|
out.append("| " + " | ".join(["---"] * len(cells)) + " |")
|
|
return out
|