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
|
(* TyXML
* http://www.ocsigen.org/tyxml
* Copyright (C) 2016 Anton Bachin
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, with linking exception;
* either version 2.1 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1301, USA.
*)
open Asttypes
open Parsetree
type assembler =
lang:Common.lang ->
loc:Location.t ->
name:string ->
expression Common.value list ->
(arg_label * expression) list
(* Helpers. *)
(* Given a parse tree [e], if [e] represents [_.txt s], where [s] is a string
constant, evaluates to [Some s]. Otherwise, evaluates to [None]. *)
let to_txt = function
| [%expr[%e? {pexp_desc = Pexp_ident f; _}]
( [%e? {pexp_desc = Pexp_ident f2; _}] [%e? arg])] -> begin
match Longident.last_exn f.txt, Longident.last_exn f2.txt, arg.pexp_desc with
| "txt", "return", Pexp_constant (Pconst_string (s, _, _)) -> Some s
| _ -> None
end
| _ -> None
(** Test if the expression is a txt containing only whitespaces. *)
let is_whitespace = function
| Common.Val e -> begin
match to_txt e with
| Some s when String.trim s = "" -> true
| _ -> false
end
| _ -> false
(* Given a list of parse trees representing children of an element, filters out
all children that consist of applications of [txt] to strings containing
only whitespace. *)
let filter_whitespace = List.filter (fun e -> not @@ is_whitespace e)
let filter_surrounding_whitespace children =
let rec aux = function
| [] -> []
| h :: t when is_whitespace h -> aux t
| l -> List.rev l
in
aux @@ aux children
(** Improve an assembler by first applying [filter_whitespace] on children
Used by the [[@@reflect.filter_whitespace]] annotation *)
let comp_filter_whitespace assembler ~lang ~loc ~name children =
assembler ~lang ~loc ~name (filter_whitespace children)
(* Given a parse tree and a string [name], checks whether the parse tree is an
application of a function with name [name]. *)
let is_element_with_name name = function
| Common.Val {pexp_desc = Pexp_apply ({pexp_desc = Pexp_ident {txt}}, _)}
when txt = name -> true
| _ -> false
(* Partitions a list of elements according to [is_element_with_name name]. *)
let partition name children =
List.partition (is_element_with_name name) children
(* Given the name [n] of a function in [Html_sigs.T], evaluates to
["Html." ^ n]. *)
let html local_name =
Longident.Ldot (Lident Common.(implementation Html), local_name)
(* Generic. *)
let nullary ~lang:_ ~loc ~name children =
if children <> [] then
Common.error loc "%s should have no content" name;
[Nolabel, [%expr ()] [@metaloc loc]]
let unary ~lang ~loc ~name children =
match children with
| [child] ->
let child = Common.wrap_value lang loc child in
[Nolabel, child]
| _ -> Common.error loc "%s should have exactly one child" name
let star ~lang ~loc ~name:_ children =
[Nolabel, Common.list_wrap_value lang loc children]
(* Special-cased. *)
let head ~lang ~loc ~name children =
let title, others = partition (html "title") children in
match title with
| [title] ->
(Nolabel, Common.wrap_value lang loc title) :: star ~lang ~loc ~name others
| _ ->
Common.error loc
"%s element must have exactly one title child element" name
let figure ~lang ~loc ~name children =
let caption, children =
let rec is_first_figcaption = function
| [] -> is_last_figcaption (List.rev children)
| h :: t ->
if is_whitespace h then is_first_figcaption t
else if is_element_with_name (html "figcaption") h then
`Top h,t
else is_last_figcaption (List.rev children)
and is_last_figcaption = function
| [] -> `No, children
| h :: t ->
if is_whitespace h then is_last_figcaption t
else if is_element_with_name (html "figcaption") h then
`Bottom h, (List.rev t)
else `No, children
in
is_first_figcaption children
in
begin match caption with
| `No -> star ~lang ~loc ~name children
| `Top elt ->
(Labelled "figcaption",
[%expr `Top [%e Common.wrap_value lang loc elt]])::
(star ~lang ~loc ~name children)
| `Bottom elt ->
(Labelled "figcaption",
[%expr `Bottom [%e Common.wrap_value lang loc elt]])::
(star ~lang ~loc ~name children)
end [@metaloc loc]
let object_ ~lang ~loc ~name children =
let params, others = partition (html "param") children in
if params <> [] then
(Labelled "params", Common.list_wrap_value lang loc params) ::
star ~lang ~loc ~name others
else
star ~lang ~loc ~name others
let audio_video ~lang ~loc ~name children =
let sources, others = partition (html "source") children in
if sources <> [] then
(Labelled "srcs", Common.list_wrap_value lang loc sources) ::
star ~lang ~loc ~name others
else
star ~lang ~loc ~name others
let table ~lang ~loc ~name children =
let caption, others = partition (html "caption") children in
let columns, others = partition (html "colgroup") others in
let thead, others = partition (html "thead") others in
let tfoot, others = partition (html "tfoot") others in
let one label = function
| [] -> []
| [child] -> [Labelled label, Common.wrap_value lang loc child]
| _ -> Common.error loc "%s cannot have more than one %s" name label
in
let columns =
if columns = [] then []
else [Labelled "columns", Common.list_wrap_value lang loc columns]
in
(one "caption" caption) @
columns @
(one "thead" thead) @
(one "tfoot" tfoot) @
(star ~lang ~loc ~name others)
let fieldset ~lang ~loc ~name children =
let legend, others = partition (html "legend") children in
match legend with
| [] -> star ~lang ~loc ~name others
| [legend] ->
(Labelled "legend", Common.wrap_value lang loc legend)::
(star ~lang ~loc ~name others)
| _ -> Common.error loc "%s cannot have more than one legend" name
let datalist ~lang ~loc ~name children =
let options, others = partition (html "option") children in
let children =
begin match others with
| [] ->
Labelled "children",
[%expr `Options [%e Common.list_wrap_value lang loc options]]
| _ ->
Labelled "children",
[%expr `Phras [%e Common.list_wrap_value lang loc children]]
end [@metaloc loc]
in
children::(nullary ~lang ~loc ~name [])
let at_most_one_child ~lang ~loc ~name children =
match children with
| [] ->
let child = Common.txt ~loc ~lang "" in
[Nolabel, child]
| [child] ->
let child = Common.wrap_value lang loc child in
[Nolabel, child]
| _ -> Common.error loc "%s can have at most one child" name
let script = at_most_one_child
(* https://html.spec.whatwg.org/#the-textarea-element *)
let textarea = at_most_one_child
let details ~lang ~loc ~name children =
let summary, others = partition (html "summary") children in
match summary with
| [summary] ->
(Nolabel, Common.wrap_value lang loc summary)::
(star ~lang ~loc ~name others)
| _ -> Common.error loc "%s must have exactly one summary child" name
let menu ~lang ~loc ~name children =
let children =
Labelled "child",
[%expr `Flows [%e Common.list_wrap_value lang loc children]]
[@metaloc loc]
in
children::(nullary ~lang ~loc ~name [])
let picture ~lang ~loc ~name children =
let img, others = partition (html "img") children in
match img with
| [] -> star ~lang ~loc ~name others
| [img] ->
(Labelled "img", Common.wrap_value lang loc img)::
(star ~lang ~loc ~name others)
| _ -> Common.error loc "%s cannot have more than one img" name
let html ~lang ~loc ~name children =
let head, others = partition (html "head") children in
let body, others = partition (html "body") others in
match head, body, others with
| [head], [body], [] ->
[Nolabel, Common.wrap_value lang loc head;
Nolabel, Common.wrap_value lang loc body]
| _ ->
Common.error loc
"%s element must have exactly head and body child elements" name
|