shorthand.ml 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. (* CSS shorthand creation based on:
  2. * - http://www.cssshorthand.com/
  3. *)
  4. open Types
  5. open Util
  6. module KM = Map.Make(struct
  7. type t = string * bool
  8. let compare a b =
  9. match a, b with
  10. | (_, false), (_, true) -> -1
  11. | (_, true), (_, false) -> 1
  12. | (base_a, _), (base_b, _) -> String.compare base_a base_b
  13. end)
  14. let order = function
  15. | "background" -> ["color"; "image"; "repeat"; "attachment"; "position-x";
  16. "position-y"]
  17. | "border" -> ["width"; "style"; "color"]
  18. | "font" -> ["style"; "variant"; "weight"; "size"; "family"]
  19. | "list-style" -> ["type"; "position"; "image"]
  20. | "outline" -> ["color"; "style"; "width"]
  21. | "margin"
  22. | "padding" -> ["top"; "right"; "bottom"; "left"]
  23. | _ -> failwith "not a shorthand property"
  24. let fold_box_dims = function
  25. | [top; right; bottom; left]
  26. when top = bottom && right = left && top = right -> [top]
  27. | [top; right; bottom; left] when top = bottom && right = left -> [top; right]
  28. | [top; right; bottom; left] when right = left -> [top; right; bottom]
  29. | dims -> dims
  30. let fold group base =
  31. let group_mem name =
  32. let rec mem = function
  33. | [] -> false
  34. | (nm, _, _) :: _ when nm = name -> true
  35. | _ :: tl -> mem tl
  36. in
  37. mem group
  38. in
  39. let group_find name =
  40. let rec wrap known = function
  41. | [] ->
  42. (match known with Some value -> value | None -> raise Not_found)
  43. | (nm, value, _) :: tl when nm = name ->
  44. wrap (Some value) tl
  45. | _ :: tl ->
  46. wrap known tl
  47. in
  48. wrap None group
  49. in
  50. let exists sub = group_mem (base ^ "-" ^ sub) in
  51. let find sub = group_find (base ^ "-" ^ sub) in
  52. let rec lookup = function
  53. | [] -> []
  54. (* `font-size` and `line-height` are slash-separated in `font` *)
  55. | "size" :: tl when base = "font" && group_mem "line-height" ->
  56. Nary ("/", [find "size"; group_find "line-height"]) :: lookup tl
  57. | name :: tl when exists name -> find name :: lookup tl
  58. | _ :: tl -> lookup tl
  59. in
  60. match base with
  61. (* `font-size` and `font-family` are required for `font` *)
  62. | "font" when exists "size" && exists "family" ->
  63. Some (Concat (lookup (order "font")))
  64. (* `border-style` is required for `border` *)
  65. | "border" when exists "style" ->
  66. Some (Concat (lookup (order "border")))
  67. (* others require at least one property, which is already the case when this
  68. * function is called *)
  69. | ("background" | "list-style" | "outline") as base ->
  70. Some (Concat (lookup (order base)))
  71. (* margin and padding can only be shorthanded when all directions are known,
  72. * merging into even shorter shorthands is done by `fold_box_dims` *)
  73. | "margin" | "padding"
  74. when exists "top" && exists "right" && exists "bottom" && exists "left" ->
  75. let dirs = [find "top"; find "right"; find "bottom"; find "left"] in
  76. Some (Concat (fold_box_dims dirs))
  77. | _ -> None
  78. let rec list_from i = function
  79. | [] when i > 0 -> raise (Invalid_argument "l")
  80. | [] -> [] (* make the compiler happy *)
  81. | l when i = 0 -> l
  82. | _ :: tl -> list_from (i - 1) tl
  83. let is_width = function
  84. | Ident ("thin" | "thick" | "medium")
  85. | Number _ -> true
  86. | _ -> false
  87. let rec unfold = function
  88. | [] -> []
  89. (* do not unfold "<shorthand>: inherit;" *)
  90. | (("background" | "border" | "font" | "list-style" | "outline" | "margin" |
  91. "padding"), Ident "inherit", _) as orig :: tl ->
  92. orig :: unfold tl
  93. (* background: [color] [image] [repeat] [attachment] [position] *)
  94. | (("background", Concat values, imp) as orig) :: tl ->
  95. let make sub value = ("background-" ^ sub, value, imp) in
  96. let id_color = function
  97. | [] -> []
  98. | [color] when Color_names.is_color color -> [make "color" color]
  99. | tl -> raise (Box_error (Expr (Concat tl), "invalid background shortcut"))
  100. in
  101. let id_repeat = function
  102. | repeat :: (Uri _ as image) :: tl ->
  103. make "repeat" repeat :: make "image" image :: id_color tl
  104. | Uri _ as image :: tl ->
  105. make "image" image :: id_color tl
  106. | tl -> id_color tl
  107. in
  108. let id_attachment = function
  109. | Ident _ as attachment :: (Ident _ as repeat) :: tl ->
  110. make "attachment" attachment :: make "repeat" repeat :: id_repeat tl
  111. | Ident ("scroll" | "fixed") as attachment :: (Uri url :: _ as tl) ->
  112. make "attachment" attachment :: id_repeat tl
  113. | (_ :: Uri _ :: _) as tl
  114. | tl -> id_repeat tl
  115. in
  116. let id_pos = function
  117. | Number _ as posy :: (Number _ as posx) :: tl
  118. | (Ident ("top" | "center" | "bottom") as posy) ::
  119. (Ident ("left" | "center" | "right") as posx) :: tl ->
  120. make "position-y" posy :: make "position-x" posx :: id_attachment tl
  121. | tl -> id_attachment tl
  122. in
  123. begin
  124. try List.rev_append (id_pos (List.rev values)) (unfold tl)
  125. (* instead of crashing, just don't unfold malformed shorthands *)
  126. with Box_error _ -> orig :: unfold tl
  127. end
  128. | ("background", (Uri _ as image), imp) :: tl ->
  129. ("background-image", image, imp) :: unfold tl
  130. | ("background", color, imp) :: tl ->
  131. ("background-color", color, imp) :: unfold tl
  132. (* border: [width] style [color] *)
  133. | ("border", Concat [Ident _ as style], imp) :: tl ->
  134. ("border-style", style, imp) :: unfold tl
  135. | ("border", Concat [width; Ident _ as style; color], imp) :: tl ->
  136. ("border-width", width, imp) ::
  137. ("border-style", style, imp) ::
  138. ("border-color", color, imp) :: unfold tl
  139. | ("border", Concat [Number _ as width; Ident _ as style], imp) :: tl ->
  140. ("border-width", width, imp) ::
  141. ("border-style", style, imp) :: unfold tl
  142. | ("border", Concat [Ident _ as style; color], imp) :: tl ->
  143. ("border-style", style, imp) ::
  144. ("border-color", color, imp) :: unfold tl
  145. (* font: [style] [variant] [weight] size[/line-height] family *)
  146. | ("font", Concat values, imp) as orig :: tl ->
  147. let replacement =
  148. let make sub value = ("font-" ^ sub, value, imp) in
  149. let identify options =
  150. let return sub = assert (List.mem sub options); sub in
  151. function
  152. | Ident "normal" -> List.hd options
  153. | Ident ("italic" | "oblique") -> return "style"
  154. | Ident "small-caps" -> return "variant"
  155. | _ -> return "weight"
  156. in
  157. match values with
  158. | [size; family] ->
  159. [make "size" size; make "family" family]
  160. | [first; size; family] ->
  161. [make (identify ["weight"; "variant"; "style"] first) first;
  162. make "size" size; make "family" family]
  163. | [first; second; size; family] ->
  164. [make (identify ["variant"; "style"] first) first;
  165. make (identify ["weight"; "variant"] second) second;
  166. make "size" size; make "family" family]
  167. | [style; variant; weight; size; family] ->
  168. [make "style" style; make "variant" variant; make "weight" weight;
  169. make "size" size; make "family" family]
  170. | _ -> [orig]
  171. in
  172. let rec split_size = function
  173. | [] -> []
  174. | ("font-size", Nary ("/", [size; line_height]), _) :: tl ->
  175. ("font-size", size, imp) ::
  176. ("line-height", line_height, imp) :: tl
  177. | hd :: tl -> hd :: split_size tl
  178. in
  179. split_size replacement @ unfold tl
  180. (* list-style: [type] [position] [image] *)
  181. | ("list-style", Concat [ltype; pos; image], imp) :: tl ->
  182. ("list-style-type", ltype, imp) ::
  183. ("list-style-position", pos, imp) ::
  184. ("list-style-image", image, imp) :: unfold tl
  185. | ("list-style", Concat [Ident _ as pos; Uri _ as image], imp) :: tl ->
  186. ("list-style-position", pos, imp) ::
  187. ("list-style-image", image, imp) :: unfold tl
  188. | ("list-style", Concat [ltype; Ident _ as pos], imp) :: tl ->
  189. ("list-style-type", ltype, imp) ::
  190. ("list-style-position", pos, imp) :: unfold tl
  191. (* margin: top right bottom left
  192. * | top right-left bottom
  193. * | top-bottom right-left
  194. * | top right bottom left
  195. * | all
  196. *)
  197. | (("margin" | "padding") as base, value, imp) :: tl ->
  198. let (top, right, bottom, left) =
  199. match value with
  200. | Concat [top; right; bottom; left] ->
  201. (top, right, bottom, left)
  202. | Concat [top; right; bottom] ->
  203. (top, right, bottom, right)
  204. | Concat [top; right] ->
  205. (top, right, top, right)
  206. | _ ->
  207. (value, value, value, value)
  208. in
  209. let make dir value = (base ^ "-" ^ dir, value, imp) in
  210. make "top" top :: make "right" right :: make "bottom" bottom ::
  211. make "left" left :: unfold tl
  212. | hd :: tl ->
  213. hd :: unfold tl
  214. let pattern = Str.regexp ("^\\(background\\|border\\|font\\|list-style" ^
  215. "\\|outline\\|padding\\|margin\\)-\\(.*\\)$")
  216. let rec make_shorthands decls =
  217. (* unfold currently existing shorthands into separate properties for merging
  218. * with override properties that are defined later on *)
  219. (*let decls = unfold decls in
  220. XXX: done by main function for correct pruning of duplicate declarations*)
  221. let rec extract_groups decl_skipped groups rest =
  222. let rec find_in_group name = function
  223. | [] -> false
  224. | (nm, _, _) :: _ when nm = name -> true
  225. | _ :: tl -> find_in_group name tl
  226. in
  227. let should_skip base name imp =
  228. try find_in_group name (KM.find (base, imp) groups)
  229. with Not_found -> false
  230. in
  231. let add base imp value =
  232. let key = base, imp in
  233. let group = try KM.find key groups with Not_found -> [] in
  234. KM.add key (value :: group) groups
  235. in
  236. function
  237. | [] -> decl_skipped, groups, rest
  238. | (("line-height", _, imp) as hd) :: tl
  239. when should_skip "font" "line_height" imp ->
  240. extract_groups true groups (hd :: rest) tl
  241. | (("line-height", _, imp) as hd) :: tl ->
  242. extract_groups decl_skipped (add "font" imp hd) rest tl
  243. | ((name, _, imp) as hd) :: tl when Str.string_match pattern name 0 ->
  244. let base = Str.matched_group 1 name in
  245. let sub = Str.matched_group 2 name in
  246. let skip_this = should_skip base name imp in
  247. if not skip_this && List.mem sub (order base)
  248. then extract_groups decl_skipped (add base imp hd) rest tl
  249. else extract_groups (decl_skipped || skip_this) groups (hd :: rest) tl
  250. | hd :: tl -> extract_groups decl_skipped groups (hd :: rest) tl
  251. in
  252. let decl_skipped, groups, rest = extract_groups false KM.empty [] decls in
  253. let replace (base, important) group tl =
  254. match fold (List.rev group) base with
  255. | Some short_value -> (base, short_value, important) :: tl
  256. | None -> List.rev_append group tl
  257. in
  258. let shorthands = KM.fold replace groups [] in
  259. let decls = List.rev_append rest shorthands in
  260. if decl_skipped then make_shorthands decls else decls
  261. let compress =
  262. Util.transform_stylesheet begin function
  263. | Statement (Ruleset (selectors, decls)) ->
  264. Statement (Ruleset (selectors, make_shorthands decls))
  265. | v -> v
  266. end
  267. let unfold_stylesheet =
  268. Util.transform_stylesheet begin function
  269. | Statement (Ruleset (selectors, decls)) ->
  270. Statement (Ruleset (selectors, unfold decls))
  271. | v -> v
  272. end