package wikitext

  1. Overview
  2. Docs

Source file wktxt_mapper.ml

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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
(**
   {!mapper} allows to implement AST rewriting using open recursion.
   A typical mapper would be based on {!default_mapper}, a deep
   identity mapper, and will fall back on it for handling the syntax it
   does not modify.
*)

open Wktxt_type

type mapper =
  { document : mapper -> document -> document
  ; block  : mapper -> block -> block
  ; table_block : mapper -> table_block -> table_block
  ; inline : mapper -> inline -> inline
  }

(**/**)

let document self = List.map (self.block self)

and block self = function
  | Header (importance, content) ->
      Header (importance, (List.map (self.inline self) content))
  | Paragraph (content) ->
      Paragraph (List.map (self.inline self) content)
  | List (content_list) ->
      List (List.map (fun l -> List.map (self.block self) l) content_list)
  | NumList (content_list) ->
      NumList (List.map (fun l -> List.map (self.block self) l) content_list)
  | DefList (content_list) ->
      DefList (List.map
        (fun (l1, l2) ->
          (List.map (self.inline self) l1, List.map (self.block self) l2))
        content_list)
  | Table (title, content_list) ->
      Table ((List.map (self.inline self) title)
             , (List.map (fun l -> List.map (self.table_block self) l) content_list))
  | Hrule | NoWikiBlock _ as x -> x

and table_block self = function
  | TableHead content -> TableHead (List.map (self.inline self) content)
  | TableItem content -> TableItem (List.map (self.inline self) content)

and inline self = function
  | Italic l -> Italic (List.map (self.inline self) l)
  | Bold l -> Bold (List.map (self.inline self) l)
  | NoWiki _ | String _ | Link _ as x -> x

(**/**)

(** A default mapper, which implements a "deep identity" mapping. *)
let default_mapper =
  { document
  ; block
  ; table_block
  ; inline
  }

let rec noformat inlines =
  let aux = function
    | Bold x -> noformat x
    | Italic x -> noformat x
    | NoWiki _ | String _ | Link _ as x -> [ x ]
  in
  List.flatten (List.map aux inlines)

(** [toc doc]
    Compute the table of contents of [doc]. This table of contents
    is computed by looking at headers. First level header is omitted.
    Table of contents is returned as un ordered list of links pointing
    to title's anchors.
*)
let toc
  : document -> (document * block) option =
  fun doc ->
  let toc_list = ref [] in
  let cnt = ref 0 in
  let id () = incr cnt ; "wikitext-header-anchor-" ^ string_of_int !cnt in
  let block self blck =
    match blck with
    | Header (d, inlines) when d <> 1 ->
      let id = id () in
      let link = (String ("<a href=\"#" ^ id ^ "\">") :: inlines) @ [String "</a>"] in
      toc_list := ((Ordered, d - 1), [link]) :: !toc_list ;
      Header (d, String ("<span id=\"" ^ id ^ "\"></span>") :: inlines)
    | _ -> default_mapper.block self blck
  in
  let mapper = { default_mapper with block } in
  let doc = mapper.document mapper doc in
  match Wktxt_parsing_functions.parse_list 0 (List.rev !toc_list) Ordered with
  | [] -> None
  | toc :: _ -> Some (doc, toc)

(** [set_toc doc]
    Replace ["__TOC__"] in [doc] by the auto-generated table of contents.
*)
let set_toc doc =
  match toc doc with
  | None -> doc
  | Some (doc, toc) ->
    let block self blck =
      match blck with
      | Paragraph [ String "__TOC__" ] -> toc
      | List _ | NumList _ | DefList _ -> blck
      | _ -> default_mapper.block self blck
    in
    let mapper = { default_mapper with block } in
    mapper.document mapper doc

(** [link sep str]
    A very basic link creation. No escaping is performed.
    Turn [str] into a link (["<a href=\"%s\">%s</a>"]).
    If [str] contains a [sep] character, everything coming before is
    used as the url, and the rest as text.
*)
let link : char -> string -> string =
  fun sep str ->
  let link url txt = Printf.sprintf "<a href=\"%s\">%s</a>" url txt in
  match String.index_opt str sep with
  | None -> link str str
  | Some space_pos ->
    link
      (String.sub str 0 space_pos)
      (String.sub str (space_pos + 1) (String.length str - space_pos - 1))

(**
   [set_links doc]
   Replace [Link] and [ExtLink] occurences by their HTML representation.
   using {!val:link}.
   [Link] uses ['|'] as separator and [ExtLink] uses [' '].
*)
let set_links doc =
  let inline self inl =
    match inl with
    | Link (1, s) -> String (link ' ' s)
    | Link (2, s) -> String (link '|' s)
    | _ -> default_mapper.inline self inl
  in
  let mapper = { default_mapper with inline } in
  mapper.document mapper doc

(** [normalize doc]
    Concatenates following [Strings] elements together
    and removes block's leading and trailing spaces. *)
let normalize doc =
  let rec concat = function
    | [] -> []
    | Bold inl :: tl -> Bold (concat inl) :: concat tl
    | Italic inl :: tl -> Italic (concat inl) :: concat tl
    | String s1 :: String s2 :: tl -> concat (String (s1 ^ s2) :: tl)
    | hd :: tl -> hd :: concat tl
  in
  let trim_right str =
    let start = String.length str - 1 in
    let rec loop position =
      if position < 0 then ""
      else match String.get str position with
        | '\n' | ' ' | '\t' -> loop (position - 1)
        | _ ->
          if position = start then str
          else String.sub str 0 (position + 1)
    in
    loop start
  in
  let trim_left str =
    let len = String.length str in
    let start = 0 in
    let rec loop position =
      if position < len then match String.get str position with
        | '\n' | ' ' | '\t' -> loop (position + 1)
        | _ ->
          if position = start then str
          else String.sub str position (len - position)
      else ""
    in loop 0
  in
  let rec trim fst = function
    | [] ->
      []
    | [ String s ] ->
      if fst then [ String (s |> trim_left |> trim_right) ]
      else [ String (trim_right s) ]
    | String s as hd :: tl ->
      if fst then String (s |> trim_left) :: trim false tl
      else hd :: trim false tl
    | hd :: tl ->
      hd :: trim false tl
  in
  let norm contents = trim true (concat contents) in
  let block self = function
    | Header (lvl, content) ->
      Header (lvl, norm content)
    | Paragraph p ->
      Paragraph (norm p)
    | DefList l ->
      DefList (List.map (fun (t, d) -> (norm t, List.map (self.block self) d)) l)
    | Table (title, tbl) ->
      Table (norm title, (List.map (List.map (self.table_block self)) tbl))
    | b ->
      default_mapper.block self b
  and table_block _ = function
    | TableHead content -> TableHead (norm content)
    | TableItem content -> TableItem (norm content)
  and inline self = function
    | Italic l -> Italic (norm l)
    | Bold l -> Bold (norm l)
    | i -> default_mapper.inline self i
  in
  let mapper = { default_mapper with block ; table_block ; inline} in
  mapper.document mapper doc
OCaml

Innovation. Community. Security.