package forester
A tool for tending mathematical forests
Install
Dune Dependency
Authors
Maintainers
Sources
4.2.0.tar.gz
md5=7543fe7acbdfeb2056dc0b774965239f
sha512=2317bf84588692bbbd40e5fa944faab4889474e4a058e336bd1165f6dd8e55e8979affab098248c87354acdc3b6e6927305553ff5ab6b002b6739719814ec080
doc/src/forester.render/Compile.ml.html
Source file Compile.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 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430
open Forester_prelude open Forester_core module type I = sig val root : string option val trees : Sem.tree Addr_map.t val run_query : Sem.query -> Addr_set.t val last_changed : addr -> Date.t option val enqueue_latex : name:string -> preamble:string -> source:string -> unit end module S = Addr_set module X = Xml_tree module F = Xml_forester module Ancestors = Algaeff.Reader.Make (struct type t = addr list end) module Current_addr = Algaeff.Reader.Make (struct type t = addr end) module Make (I : I) () = struct let mainmatter_cache : (addr, X.content) Hashtbl.t = Hashtbl.create 1000 module Xmlns_map = struct type t = {prefix_to_xmlns : string String_map.t; xmlns_to_prefixes : string list String_map.t} let empty = {prefix_to_xmlns = String_map.empty; xmlns_to_prefixes = String_map.empty} let assoc ~prefix ~xmlns env = {prefix_to_xmlns = String_map.add prefix xmlns env.prefix_to_xmlns; xmlns_to_prefixes = String_map.add_to_list xmlns prefix env.xmlns_to_prefixes} end module Xmlns_prefixes = Algaeff.Reader.Make (Xmlns_map) let get_xmlns_for_prefix prefix = let env = Xmlns_prefixes.read () in String_map.find_opt prefix env.prefix_to_xmlns let rec normalise_prefix ?loc ~prefix ~xmlns kont = match xmlns with | Some xmlns -> begin let open Xmlns_map in let env = Xmlns_prefixes.read () in let exception Shadowing in try begin match String_map.find_opt prefix env.prefix_to_xmlns, String_map.find_opt xmlns env.xmlns_to_prefixes with | None, (None | Some []) -> let env = assoc ~prefix ~xmlns env in Xmlns_prefixes.run ~env @@ fun () -> kont @@ ([(prefix, xmlns)], prefix) | Some xmlns', Some prefixes -> if xmlns' = xmlns && List.mem prefix prefixes then kont ([], prefix) else raise Shadowing | _, Some (prefix' :: _) -> kont ([], prefix') | Some xmlns', None -> raise Shadowing end with Shadowing -> normalise_prefix ?loc ~prefix:(prefix ^ "_") ~xmlns:(Some xmlns) kont end | _ -> kont ([], prefix) let is_root addr = Some addr = Option.map (fun x -> User_addr x) I.root let addr_peek_title scope = Option.bind (Addr_map.find_opt scope I.trees) Sem.Util.peek_title let get_tree addr = Addr_map.find_opt addr I.trees let get_sorted_trees addrs : Sem.tree list = let find addr = match get_tree addr with | None -> [] | Some doc -> [doc] in Sem.Util.sort @@ List.concat_map find @@ S.elements addrs let get_trees_from_query query = get_sorted_trees @@ I.run_query query let contributors scope = let by_title = Compare.under addr_peek_title @@ Compare.option String.compare in List.sort by_title @@ S.elements @@ I.run_query @@ Sem.Query.hereditary_contributors scope let compile_date (date : Date.t) = let addr = let addr = User_addr (Format.asprintf "%a" Date.pp date) in get_tree addr |> Option.map @@ fun _doc -> addr in let year = Date.year date in let month = Date.month date in let day = Date.day date in X.Date {addr; year; month; day} let compile_dates = List.map compile_date let rec compile_located (located : Sem.node Range.located) = match located.value with | Sem.Text txt -> [X.Text txt] | Sem.Verbatim txt -> [X.CDATA txt] | Sem.Prim (p, xs) -> compile_prim p xs | Sem.Math (mode, xs) -> let body = let module TP = Render_TeX_like.Printer in Str.global_replace (Str.regexp "\n") " " @@ TP.contents @@ Render_TeX_like.render ~cfg:{tex = false} xs in let display = match mode with | Inline -> `Inline | Display -> `Block in [X.TeX {display; body}] | Sem.Link (addr, title, modifier) -> begin match get_tree addr with | Some tree -> compile_internal_link ~title ~modifier ~addr ~dest:tree | None -> let url = Format.asprintf "%a" pp_addr addr in compile_external_link ~title ~modifier ~url end | Sem.Ref addr -> begin match get_tree addr with | None -> Reporter.fatalf ?loc:located.loc Tree_not_found "could not find tree at address `%a` for reference" pp_addr addr | Some tree -> let taxon = tree.fm.taxon |> Option.map String_util.sentence_case in let number = tree.fm.number in [X.Ref {addr; taxon; number}] end | Sem.Img path -> [X.Img {src = path}] | Sem.Xml_tag (name, attrs, xs) -> let rec fold_attrs tag_prefix updates acc attrs = match attrs with | [] -> let xmlns_attrs = updates |> List.map @@ fun (prefix, xmlns) -> X.{key = X.{prefix = "xmlns"; uname = prefix; xmlns = None}; value = xmlns} in let name = X.{prefix = tag_prefix; uname = name.uname; xmlns = get_xmlns_for_prefix tag_prefix} in let attrs = xmlns_attrs @ List.rev acc in let content = compile_nodes xs in X.Xml_tag {name; attrs; content} | (k, v) :: attrs -> normalise_prefix ?loc:located.loc ~prefix:k.prefix ~xmlns:k.xmlns @@ fun (updates', prefix) -> let xml_attr = X.{key = X.{prefix; uname = k.uname; xmlns = None}; value = Render_text.Printer.contents @@ Render_text.render ~trees:I.trees v} in fold_attrs tag_prefix (updates @ updates') (xml_attr :: acc) attrs in [normalise_prefix ~prefix:name.prefix ~xmlns:name.xmlns @@ fun (updates, tag_prefix) -> fold_attrs tag_prefix updates [] attrs] | Sem.Embed_tex {preamble; source} -> let as_tex x = Render_TeX_like.Printer.contents @@ Render_TeX_like.render ~cfg:{tex = true} x in let preamble = as_tex preamble in let source = as_tex source in let hash = Digest.to_hex @@ Digest.string @@ preamble ^ source in I.enqueue_latex ~name:hash ~preamble ~source; [X.Embedded_tex {hash; preamble; source}] | Sem.Transclude (opts, addr) -> begin match get_tree addr with | None -> Reporter.fatalf ?loc:located.loc Tree_not_found "could not find tree at address `%a` for transclusion" pp_addr addr | Some doc -> compile_transclusion ~opts doc end | Sem.Subtree (opts, subtree) -> compile_transclusion ~opts subtree | Sem.Query_tree (opts, query) -> begin match get_trees_from_query query with | [] -> [X.Prim (`P, X.Content [X.Info "Query returned no results"])] | trees -> trees |> List.concat_map @@ fun tree -> let opts = Sem.{expanded = false; show_heading = true; title_override = None; taxon_override = None; toc = false; numbered = false; show_metadata = true} in compile_transclusion ~opts tree end | Sem.TeX_cs name -> Reporter.fatalf ?loc:located.loc Resolution_error "unresolved control sequence `\\%a`" TeX_cs.pp name and compile_transclusion ~opts (tree : Sem.tree) = let current = Current_addr.read () in let update old_ancestors = current :: old_ancestors in Ancestors.scope update @@ fun () -> [X.Subtree (compile_tree_inner ~opts tree)] and compile_title ~(opts : Sem.transclusion_opts) (fm : Sem.frontmatter) = let trees = I.trees in let ancestors = Ancestors.read () in let title = match opts.title_override with | Some title -> Some title | None -> fm.title |> Option.map @@ Render_util.expand_title_with_parents ~trees ~ancestors fm in begin title |> Option.map @@ fun title -> compile_nodes @@ Sem.sentence_case title end, begin title |> Option.map @@ fun title -> String_util.sentence_case @@ Render_text.Printer.contents @@ Render_text.render ~trees title end and compile_attributions ~contributors ~ = match authors, contributors with | [], [] -> [] | _ -> List.map compile_author authors @ List.map compile_contributor contributors and = X.Author (compile_attribution_inner author) and compile_contributor = X.Contributor (compile_attribution_inner author) and compile_attribution_inner = let exception Untitled in try match get_tree author with | None -> raise Untitled | Some biotree -> let content = match biotree.fm.title with | None -> raise Untitled | Some title -> compile_nodes title in let title = biotree.fm.title |> Option.map Sem.string_of_nodes in X.Content [X.Local_link {title; addr = author; content}] with Untitled -> let name = Format.asprintf "%a" pp_addr author in X.Content [X.Text name] and compile_meta (key, body) = let body = compile_nodes body in X.Meta {key; body} and compile_frontmatter ~opts (fm : Sem.frontmatter) = let anchor = Option.some @@ string_of_int @@ Oo.id (object end) in let title, title_text = compile_title ~opts fm in let number = fm.number in let taxon = Option.map String_util.sentence_case @@ match opts.taxon_override with | Some taxon -> Some taxon | None -> fm.taxon in let source_path = fm.source_path in let addr = Option.some fm.addr in let designated_parent = fm.designated_parent |> Option.map @@ fun addr -> Format.asprintf "%a" pp_addr addr in let dates = compile_dates fm.dates in let contributors = contributors fm.addr in let attributions = compile_attributions ~contributors ~authors:fm.authors in let last_changed = I.last_changed fm.addr |> Option.map compile_date in let metas = fm.metas |> List.map compile_meta in X.{title; title_text; anchor; number; taxon; designated_parent; metas; addr; source_path; dates; last_changed; attributions} and compile_tree_inner ?(include_backmatter = false) ~opts (tree : Sem.tree) = Current_addr.run ~env:tree.fm.addr @@ fun () -> let ancestors = Ancestors.read () in let options = X.{toc = opts.toc; numbered = opts.numbered; show_heading = opts.show_heading; show_metadata = opts.show_metadata; expanded = opts.expanded; root = is_root tree.fm.addr} in let frontmatter = compile_frontmatter ~opts tree.fm in let mainmatter = begin match tree.fm.addr with | addr when List.mem addr ancestors -> X.Content [X.Prim (`P, X.Content [X.Info "Transclusion cycle"])] | addr -> match Hashtbl.find_opt mainmatter_cache addr with | Some cached -> cached | None -> let result = compile_nodes tree.body in Hashtbl.add mainmatter_cache addr result; result end; in let backmatter = if include_backmatter && not (is_root tree.fm.addr) then compile_backmatter tree.fm.addr tree.bm else [] in X.Tree {options; frontmatter; mainmatter; backmatter} and compile_backmatter addr bm = let opts = {Sem.default_transclusion_opts with numbered = false} in let compile_trees = List.map @@ fun tree -> X.splice_tree @@ compile_tree_inner ~opts tree in bm |> List.filter_map @@ function | Sem.Backmatter_section {title; query} -> let title = Sem.sentence_case title in let title_content = Option.some @@ compile_nodes title in let title_text = Option.some @@ Render_text.Printer.contents @@ Render_text.render ~trees:I.trees title in match compile_trees @@ get_trees_from_query query with | [] -> None | trees -> let options = X.{toc = false; expanded = true; numbered = false; show_heading = true; show_metadata = false; root = false} in let frontmatter = X.{title = title_content; title_text; anchor = None; number = None; taxon = None; designated_parent = None; metas = []; addr = None; source_path = None; dates = []; last_changed = None; attributions = []} in let mainmatter = X.Content begin trees |> List.map @@ fun tree -> let options = X.{tree.options with expanded = false} in let tree = X.{tree with options} in X.Subtree (X.Tree tree) end in Option.some @@ X.{options; frontmatter; mainmatter; backmatter = []} and compile_internal_link ~title ~modifier ~addr ~dest = let trees = I.trees in let ancestors = Ancestors.read () in let dest_title = dest.fm.title |> Option.map @@ Render_util.expand_title_with_parents ~trees ~ancestors dest.fm in let content = title |> Option.fold ~none:dest_title ~some:Option.some |> Option.map (Sem.apply_modifier modifier) |> Option.value ~default:[Range.locate_opt None @@ Sem.Text "Untitled"] |> compile_nodes in let title = match dest_title with | None -> None | Some t -> let title_string = String_util.sentence_case @@ Render_text.Printer.contents @@ Render_text.render ~trees t in Some title_string in [X.Local_link {title; content; addr = addr}] and compile_external_link ~title ~modifier ~url = let href = url in let content = title |> Option.map (Sem.apply_modifier modifier) |> Option.value ~default:[Range.locate_opt None @@ Sem.Text url] |> compile_nodes in [X.External_link {href; content; title = None}]; and compile_nodes (xs : Sem.t) = X.Content (List.concat_map compile_located xs) and compile_prim p xs = let content = compile_nodes xs in [X.Prim (p, content)] let compile_tree tree = Ancestors.run ~env:[] @@ fun () -> let env = Xmlns_map.assoc ~prefix:F.reserved_prefix ~xmlns:F.forester_xmlns Xmlns_map.empty in Xmlns_prefixes.run ~env @@ fun () -> compile_tree_inner ~include_backmatter:true ~opts:Sem.default_transclusion_opts tree end
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>