-
-
Notifications
You must be signed in to change notification settings - Fork 951
/
Copy pathflame_markdown.dart
85 lines (78 loc) · 2.86 KB
/
flame_markdown.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
import 'package:flame/text.dart';
import 'package:markdown/markdown.dart';
/// Helper to parse markdown strings into an AST structure provided by the
/// `markdown` package, and convert that structure into an equivalent
/// [DocumentRoot] from Flame.
///
/// This allows for the creation of rich-text components on Flame using a
/// very simple and easy-to-write markdown syntax.
///
/// Note more advanced markdown features are not supported, such as tables,
/// code blocks, images, and inline HTML.
/// It is also possible that some otherwise valid markdown nestings of
/// block and inline-type elements are not currently supported.
class FlameMarkdown {
/// Converts a markdown string to a [DocumentRoot] from Flame.
///
/// This uses the `markdown` package to parse the markdown string
/// into an AST structure, and then converts that structure into
/// a [DocumentRoot] from Flame.
static DocumentRoot toDocument(String markdown, {Document? document}) {
final nodes = _parse(markdown, document: document);
return DocumentRoot(
nodes.map(_convertNode).map(_castCheck<BlockNode>).toList(),
);
}
static List<Node> _parse(String markdown, {Document? document}) {
return (document ?? Document()).parse(markdown);
}
static TextNode _convertNode(Node node) {
if (node is Element) {
return _convertElement(node);
} else if (node is Text) {
return _convertText(node);
} else {
throw Exception('Unknown node type: ${node.runtimeType}');
}
}
static TextNode _convertElement(Element element) {
final children = (element.children ?? [])
.map(_convertNode)
.map(_castCheck<InlineTextNode>)
.toList();
final child = _groupInlineChildren(children);
return switch (element.tag) {
'h1' => HeaderNode(child, level: 1),
'h2' => HeaderNode(child, level: 2),
'h3' => HeaderNode(child, level: 3),
'h4' => HeaderNode(child, level: 4),
'h5' => HeaderNode(child, level: 5),
'h6' => HeaderNode(child, level: 6),
'p' => ParagraphNode(child),
'em' || 'i' => ItalicTextNode(child),
'strong' || 'b' => BoldTextNode(child),
'code' => CodeTextNode(child),
_ => throw Exception('Unknown element tag: ${element.tag}'),
} as TextNode;
}
static PlainTextNode _convertText(Text text) {
return PlainTextNode(text.text);
}
static InlineTextNode _groupInlineChildren(List<InlineTextNode> children) {
if (children.isEmpty) {
throw 'Invalid markdown structure: Found block element with no children';
} else if (children.length == 1) {
return children.single;
} else {
return GroupTextNode(children);
}
}
static T _castCheck<T extends TextNode>(TextNode node) {
if (node is T) {
return node;
} else {
throw 'Invalid markdown structure: '
'Expected $T but got ${node.runtimeType}';
}
}
}