from typing import Iterable from mistletoe import Document, block_token, span_token from mistletoe.token import Token from pydantic import BaseModel from app.libs.adf.schema import ( ADFDocument, DocNode, Version, HeadingNode, HeadingAttrs, ParagraphNode, BulletListNode, OrderedListNode, ListItemNode, CodeBlockNode, TextNode, CodeBlockAttrs, CodeMark, EmMark, StrongMark, LinkMark, LinkMarkAttrs, StrikeMark, BlockquoteNode, TextMark, ) def convert_markdown_to_adf_document(md: str) -> ADFDocument: md_doc = Document(md) return ADFDocument(root=convert_markdown_node(md_doc)) def convert_markdown_node( node: Token | None, parent_marks: list[TextMark] = [] ) -> BaseModel | None: if node is None: return None if isinstance(node, block_token.Document): c = convert_markdown_nodes(node.children) return DocNode(version=Version.V1, content=c) elif isinstance(node, block_token.Heading): return HeadingNode( content=convert_markdown_nodes(node.children), attrs=HeadingAttrs(level=node.level), ) elif isinstance(node, block_token.Paragraph): return ParagraphNode(content=convert_markdown_nodes(node.children)) elif isinstance(node, block_token.Quote): return BlockquoteNode(content=convert_markdown_nodes(node.children)) elif isinstance(node, block_token.List): first_item: block_token.ListItem = next(iter(node.children)) is_bullet_list = first_item.leader == "-" if is_bullet_list: return BulletListNode(content=convert_markdown_nodes(node.children)) else: return OrderedListNode(content=convert_markdown_nodes(node.children)) elif isinstance(node, block_token.ListItem): return ListItemNode(content=convert_markdown_nodes(node.children)) elif isinstance(node, block_token.CodeFence): return CodeBlockNode( content=[TextNode(text=node.content)], attrs=CodeBlockAttrs(language=node.language), ) elif isinstance(node, block_token.BlockCode): return None elif isinstance(node, span_token.RawText): return TextNode( text=node.content, marks=parent_marks, ) elif isinstance(node, span_token.LineBreak): return None elif isinstance(node, span_token.InlineCode): marks = parent_marks.copy() marks.append(CodeMark()) return convert_markdown_node(next(iter(node.children)), marks) elif isinstance(node, span_token.Emphasis): marks = parent_marks.copy() marks.append(EmMark()) return convert_markdown_node(next(iter(node.children)), marks) elif isinstance(node, span_token.Strong): marks = parent_marks.copy() marks.append(StrongMark()) return convert_markdown_node(next(iter(node.children)), marks) elif isinstance(node, span_token.Strikethrough): marks = parent_marks.copy() marks.append(StrikeMark()) return convert_markdown_node(next(iter(node.children)), marks) elif isinstance(node, span_token.Link): marks = parent_marks.copy() marks.append( LinkMark( attrs=LinkMarkAttrs( href=node.target, title=node.title, ) ) ) return convert_markdown_node(next(iter(node.children)), marks) else: print(f"unsupported node: {node}") def convert_markdown_nodes( md_nodes: Iterable[Token] | None, marks: list[TextMark] = [] ) -> list[BaseModel] | None: if md_nodes is None: return None adf_nodes = [] for md_node in md_nodes: adf_node = convert_markdown_node(md_node, marks) if adf_node is not None: adf_nodes.append(adf_node) return adf_nodes