package dream-html

  1. Overview
  2. Docs

Constructing HTML. Detailed explanation in https://github.com/yawaramin/dream-html.

Let's adapt the example from the Dream home page:

let hello who =
  let open Dream_html in
  let open HTML in
  html [] [body [] [h1 [] [txt "Hello, %s!" who]]]

let () =
  Dream.run
  @@ Dream.logger
  @@ Dream.router [Dream.get "/" (fun _ -> Dream_html.respond (hello "world"))]

More examples shown below.

Core types

These are the types of the final values which get rendered.

type attr

E.g. id="toast".

type node

Either a tag, a comment, or text data in the markup.

Output

val to_string : node -> string
val pp : Stdlib.Format.formatter -> node -> unit
val respond : ?status:[< Dream.status ] -> ?code:int -> ?headers:(string * string) list -> node -> Dream.response Dream.promise
val set_body : Dream.response -> node -> unit

Type-safe wrapper for Dream.set_body. Sets the body to the given node and sets the Content-Type header to text/html.

val write : Dream.stream -> node -> unit Dream.promise

Type-safe wrapper for Dream.write.

Constructing nodes and attributes

type 'a to_attr = 'a -> attr

Attributes can be created from typed values.

type 'a string_attr = ('a, unit, string, attr) Stdlib.format4 -> 'a

Special handling for string-value attributes so they can use format strings i.e. string interpolation.

type std_tag = attr list -> node list -> node

A 'standard' tag with attributes and children.

type void_tag = attr list -> node
type 'a text_tag = attr list -> ('a, unit, string, node) Stdlib.format4 -> 'a

Tags which can have attributes but can contain only text. The text can be formatted.

val attr : string -> attr

attr name is a new attribute which does not carry any payload. E.g.

let required = attr "required"
  • since 0.1.0.
val string_attr : string -> ?raw:bool -> _ string_attr

string_attr name fmt is a new string-valued attribute which allows formatting i.e. string interpolation of the value. Note, the fmt argument is required due to the value restriction.

val uri_attr : string -> _ string_attr

Convenience for attributes whose values should be URIs. Takes care of URI- encoding.

a [href "/blog?tags=iamsafe\"></a><script>alert('Pwned')</script>"] [txt "Tags: tag1 | tag2"]

Output:

<a href="/blog?tags=iamsafe%22%3E%3C/a%3E%3Cscript%3Ealert('Pwned')%3C/script%3E">Tags: tag1 | tag2</a>
val bool_attr : string -> bool to_attr
val float_attr : string -> float to_attr
val int_attr : string -> int to_attr
val std_tag : string -> std_tag
val void_tag : string -> void_tag
val text_tag : string -> ?raw:bool -> _ text_tag

Build a tag which can contain only text.

val txt : ?raw:bool -> ('a, unit, string, node) Stdlib.format4 -> 'a

A text node inside the DOM e.g. the 'hi' in <b>hi</b>. Allows string interpolation using the same formatting features as Printf.sprintf:

b [] [txt "Hello, %s!" name]

Or without interpolation:

b [] [txt "Bold of you."]

HTML-escapes the text value using Dream.html_escape. You can use the ~raw param to bypass escaping:

let user_input = "<script>alert('I like HTML injection')</script>" in
txt ~raw:true "%s" user_input
val comment : string -> node

A comment that will be embedded in the rendered HTML, i.e. <!-- comment -->. The text is HTML-escaped.

val csrf_tag : Dream.request -> node

Convenience to add a CSRF token generated by Dream into your form. Type-safe wrapper for Dream.csrf_tag.

form
  [action "/foo"]
  [csrf_tag req; input [name "bar"]; input [type_ "submit"]]

Accessors for tags

val (+@) : node -> attr -> node

Add an attribute to a tag.

let toast msg = p [id "toast"] [txt "%s" msg]
let toast_oob = toast "ok." +@ Hx.swap_oob "true"
  • raises Invalid_argument

    if the node is not a tag (i.e. if it is a text or comment node).

  • since 0.0.3.
val (-@) : node -> string -> node

Remove an attribute from a tag.

  • raises Invalid_argument

    if the node is not a tag (i.e. if it is a text or comment node).

  • since 0.0.3.
val (.@[]) : node -> string -> string

Get the value of an existing attribute.

let toast = p [id "toast"] [txt "OK."]
let toast_id = toast.@["id"]
  • raises Invalid_argument

    if the node is not a tag (i.e. if it is a text or comment node).

  • raises Not_found

    if the tag does not have the given attribute.

  • since 0.0.3.
val is_null : node -> bool

Get whether a node is null (empty) or not. Useful for conditional rendering of UIs when you are passed in a node and you don't know if it's empty or not.

  • since 1.2.0.
val is_null_ : attr -> bool

Get whether an attribute is null (empty) or not.

  • since 1.2.0.

Standard attributes and tags

module HTML : sig ... end

All standard HTML attributes and tags. Some attributes and tags have the same name, e.g. style. To disambiguate them, attributes have a _ (underscore) suffix.

module SVG : sig ... end
module MathML : sig ... end

ARIA support

htmx support

module Hx : sig ... end

Interop example–Markdown to HTML

In this section we show an extended example of interoperability of dream-html with other formats, e.g. the Markdown format defined by the omd library:

open Dream_html
open HTML

let omd_attr (k, opt_v) =
  string_attr k "%s" @@ Option.fold ~none:"" ~some:Fun.id opt_v

let rec omd_node elements = elements |> List.map element_node |> null
and item omd = li [] [omd_node omd]

and element_node = function
  | Omd.H1 omd -> h1 [] [omd_node omd]
  | H2 omd -> h2 [] [omd_node omd]
  | H3 omd -> h3 [] [omd_node omd]
  | H4 omd -> h4 [] [omd_node omd]
  | H5 omd -> h5 [] [omd_node omd]
  | H6 omd -> h6 [] [omd_node omd]
  | Paragraph omd -> p [] [omd_node omd]
  | Text str -> txt "%s" str
  | Emph omd -> em [] [omd_node omd]
  | Bold omd -> b [] [omd_node omd]
  | Ul omds | Ulp omds -> ul [] @@ List.map item omds
  | Ol omds | Olp omds -> ol [] @@ List.map item omds
  | Code (cls, str) -> code [class_ "%s" cls] [txt "%s" str]
  | Code_block (cls, str) ->
    pre [class_ "%s" cls] [code [class_ "%s" cls] [txt "%s" str]]
  | Br -> br []
  | Hr -> hr []
  | NL -> txt "\n"
  | Url (hre, omd, titl) ->
    a
      [ href "%s" hre;
        target "_blank";
        class_ "after:content-['_↗️']";
        title "%s" titl ]
      [omd_node omd]
  | Html ("details", attrs, omd) ->
    details (List.map omd_attr attrs) [omd_node omd]
  | Html ("summary", attrs, omd) ->
    summary (List.map omd_attr attrs) [omd_node omd]
  | Html_block ("details", attrs, omd) ->
    details (List.map omd_attr attrs) [omd_node omd]
  | Html_block ("summary", attrs, omd) ->
    summary (List.map omd_attr attrs) [omd_node omd]
  | Ref (_, _, _, _)
  | Img_ref (_, _, _, _)
  | Html (_, _, _)
  | Html_block (_, _, _)
  | X _ -> null []
  | Html_comment str -> comment str
  | Raw str -> txt ~raw:true "%s" str
  | Raw_block str -> pre [] [txt ~raw:true "%s" str]
  | Blockquote omd -> blockquote [] [omd_node omd]
  | Img (al, sr, titl) -> img [alt "%s" al; src "%s" sr; title "%s" titl]

The entrypoint of the functionality is the omd_node function, which converts from the Omd.t value which can be obtained by, for example, parsing Markdown with the Omd.of_string function, to a type-safe node value.

In the element_node function we traverse the Markdown structure and convert it into specific nodes–tags, comments, etc.. Text is escaped as appropriate and some specific conversions are handled specially:

  • All hyperlinks are made to open new tabs and suffixed using a Tailwind CSS style class.
  • We support the <details> and <summary> HTML tags, but no others.

This is just an example, but it shows the idea that we can take various formats and interop with them using type-sdfe HTML generated by dream-html.

OCaml

Innovation. Community. Security.