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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
open Ast.Util
let rec remove_links inline =
match inline with
| Concat (attr, inlines) -> Concat (attr, List.map remove_links inlines)
| Emph (attr, inline) -> Emph (attr, remove_links inline)
| Strong (attr, inline) -> Emph (attr, remove_links inline)
| Link (_, link) -> link.label
| Image (attr, link) ->
Image (attr, { link with label = remove_links link.label })
| Hard_break _ | Soft_break _ | Html _ | Code _ | Text _ -> inline
let =
let remove_links_f = remove_links in
fun ?(remove_links = false) doc ->
let = ref [] in
let rec loop blocks =
List.iter
(function
| Heading (attr, level, inline) ->
let inline =
if remove_links then remove_links_f inline else inline
in
headers := (attr, level, inline) :: !headers
| Blockquote (_, blocks) -> loop blocks
| List (_, _, _, block_lists) -> List.iter loop block_lists
| Paragraph _ | Thematic_break _ | Html_block _ | Definition_list _
| Code_block _ | Table _ ->
())
blocks
in
loop doc;
List.rev !headers
let rec find_start level number subsections =
match headers with
| (_, , _) :: tl when header_level > level ->
if number = 0 then
match subsections with
| [] -> headers
| n :: subsections -> find_start headers (level + 1) n subsections
else find_start tl level number subsections
| (_, , _) :: tl when header_level = level ->
if number <= 1 then
match subsections with
| [] -> tl
| n :: subsections -> find_start tl (level + 1) n subsections
else find_start tl level (number - 1) subsections
| _ ->
[]
let unordered_list items = List ([], Bullet '*', Tight, items)
let find_id attributes =
List.find_map
(function k, v when String.equal "id" k -> Some v | _ -> None)
attributes
let link attributes label =
let inline =
match find_id attributes with
| None -> label
| Some id -> Link ([], { label; destination = "#" ^ id; title = None })
in
Paragraph ([], inline)
let rec make_toc
( : ('attr * int * 'a inline) list)
~min_level
~max_level =
match headers with
| _ when min_level > max_level -> ([], headers)
| [] -> ([], [])
| (_, level, _) :: _ when level < min_level -> ([], headers)
| (_, level, _) :: tl when level > max_level ->
make_toc tl ~min_level ~max_level
| (attr, level, t) :: tl when level = min_level ->
let sub_toc, tl = make_toc tl ~min_level:(min_level + 1) ~max_level in
let toc_entry =
match sub_toc with
| [] -> [ link attr t ]
| _ -> [ link attr t; unordered_list sub_toc ]
in
let toc, tl = make_toc tl ~min_level ~max_level in
(toc_entry :: toc, tl)
| _ ->
let sub_toc, tl =
make_toc headers ~min_level:(min_level + 1) ~max_level
in
let toc, tl = make_toc tl ~min_level ~max_level in
([ unordered_list sub_toc ] :: toc, tl)
let toc ?(start = []) ?(depth = 2) doc =
if depth < 1 then invalid_arg "Omd.toc: ~depth must be >= 1";
let = headers ~remove_links:true doc in
let =
match start with
| [] -> headers
| number :: _ when number < 0 ->
invalid_arg "Omd.toc: level 1 start must be >= 0"
| number :: subsections -> find_start headers 1 number subsections
in
let len = List.length start in
let toc, _ = make_toc headers ~min_level:(len + 1) ~max_level:(len + depth) in
match toc with [] -> [] | _ -> [ unordered_list toc ]