stringify.ml 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. open Types
  2. open Util
  3. let tab = " "
  4. let indent = Str.global_replace (Str.regexp "^\\(.\\)") (tab ^ "\\1")
  5. let prefix_space = function "" -> "" | s -> " " ^ s
  6. let rec cat sep fn = function
  7. | [] -> ""
  8. | [hd] -> fn hd
  9. | hd :: tl -> fn hd ^ sep ^ cat sep fn tl
  10. (*
  11. * Pretty-printing
  12. *)
  13. let string_of_num n =
  14. if is_int n
  15. then string_of_int (int_of_float n)
  16. else string_of_float n
  17. let rec string_of_expr = function
  18. | Ident id -> id
  19. | Strlit str -> "\"" ^ str ^ "\""
  20. | Uri uri when String.contains uri ')' -> "url(\"" ^ uri ^ "\")"
  21. | Uri uri -> "url(" ^ uri ^ ")"
  22. | Concat values -> cat " " string_of_expr values
  23. | Number (n, None) -> string_of_num n
  24. | Number (n, Some u) -> string_of_num n ^ u
  25. | Function (name, arg) -> name ^ "(" ^ string_of_expr arg ^ ")"
  26. | Hexcolor hex -> "#" ^ hex
  27. | Unary (op, opnd) -> op ^ string_of_expr opnd
  28. | Nary (",", opnds) -> cat ", " string_of_expr opnds
  29. | Nary (op, opnds) -> cat op string_of_expr opnds
  30. | Key_value (key, op, value) -> key ^ op ^ string_of_expr value
  31. let string_of_declaration (name, value, important) =
  32. let imp = if important then " !important" else "" in
  33. name ^ ": " ^ string_of_expr value ^ imp ^ ";"
  34. let rec stringify_selector w selector =
  35. let str = stringify_selector w in
  36. match selector with
  37. | No_element -> ""
  38. | All_elements -> "*"
  39. | Element elem -> elem
  40. | Id (base, id) ->
  41. str base ^ "#" ^ id
  42. | Class (base, cls) ->
  43. str base ^ "." ^ cls
  44. | Attribute (base, attr, None) ->
  45. str base ^ "[" ^ attr ^ "]"
  46. | Attribute (base, attr, Some (op, value)) ->
  47. str base ^ "[" ^ attr ^ w ^ op ^ w ^ string_of_expr value ^ "]"
  48. | Pseudo_class (base, cls, None) ->
  49. str base ^ ":" ^ cls
  50. | Pseudo_class (base, fn, Some args) ->
  51. str base ^ ":" ^ fn ^ "(" ^ cat ("," ^ w) (stringify_arg w) args ^ ")"
  52. | Pseudo_element (base, elem) ->
  53. str base ^ "::" ^ elem
  54. | Combinator (left, " ", right) ->
  55. str left ^ " " ^ str right
  56. | Combinator (left, com, right) ->
  57. str left ^ w ^ com ^ w ^ str right
  58. and stringify_arg w = function
  59. | Nested_selector s -> stringify_selector w s
  60. | Nth nth -> stringify_nth w nth
  61. and stringify_nth w = function
  62. | Even -> "even"
  63. | Odd -> "odd"
  64. | Formula (0, b) -> string_of_int b
  65. | Formula (a, b) ->
  66. begin
  67. match a with
  68. | 1 -> "n"
  69. | -1 -> "-n"
  70. | a -> string_of_int a ^ "n"
  71. end ^ begin
  72. match b with
  73. | 0 -> ""
  74. | b when b < 0 -> w ^ "-" ^ w ^ string_of_int (-b)
  75. | b -> w ^ "+" ^ w ^ string_of_int b
  76. end
  77. let string_of_selector = stringify_selector " "
  78. let string_of_media_expr = function
  79. | (feature, None) -> "(" ^ feature ^ ")"
  80. | (feature, Some value) -> "(" ^ feature ^ ": " ^ string_of_expr value ^ ")"
  81. let string_of_media_query =
  82. let features_str = cat " and " string_of_media_expr in
  83. function
  84. | (None, None, []) -> ""
  85. | (None, Some mtype, []) -> mtype
  86. | (Some pre, Some mtype, []) -> pre ^ " " ^ mtype
  87. | (None, None, features) -> features_str features
  88. | (None, Some mtype, features) -> mtype ^ " and " ^ features_str features
  89. | (Some pre, Some mtype, features) ->
  90. pre ^ " " ^ mtype ^ " and " ^ features_str features
  91. | (Some pre, None, _) ->
  92. failwith "unexpected media query prefix \"" ^ pre ^ "\""
  93. let stringify_condition w c =
  94. let rec transform =
  95. let p c = `Parens (transform c) in
  96. function
  97. | Not c -> `Not (p c)
  98. | And c -> `And (List.map p c)
  99. | Or c -> `Or (List.map p c)
  100. | Decl (name, value) -> `Decl (name, value)
  101. in
  102. let rec str = function
  103. | `Not c -> "not " ^ str c
  104. | `And c -> cat " and " str c
  105. | `Or c -> cat " or " str c
  106. | `Decl (name, value) -> "(" ^ name ^ ":" ^ w ^ string_of_expr value ^ ")"
  107. | `Parens (`Decl _ as d) -> str d
  108. | `Parens c -> "(" ^ str c ^ ")"
  109. in
  110. str (transform c)
  111. let string_of_condition = stringify_condition " "
  112. let block = function "" -> " {}" | body -> " {\n" ^ indent body ^ "\n}"
  113. let string_of_descriptor_declaration (name, value) =
  114. name ^ ": " ^ string_of_expr value ^ ";"
  115. let string_of_keyframe_ruleset (expr, decls) =
  116. string_of_expr expr ^ block (cat "\n" string_of_declaration decls)
  117. let rec string_of_statement = function
  118. | Ruleset (selectors, decls) ->
  119. cat ", " string_of_selector selectors ^
  120. block (cat "\n" string_of_declaration decls)
  121. | Media (queries, rulesets) ->
  122. "@media" ^ prefix_space (cat ", " string_of_media_query queries) ^
  123. block (cat "\n\n" string_of_statement rulesets)
  124. | Import (target, []) ->
  125. "@import " ^ string_of_expr target ^ ";"
  126. | Import (target, queries) ->
  127. "@import " ^ string_of_expr target ^ " " ^ cat ", " string_of_media_query queries ^ ";"
  128. | Charset charset ->
  129. "@charset \"" ^ charset ^ "\";"
  130. | Page (None, decls) ->
  131. "@page" ^ block (cat "\n" string_of_declaration decls)
  132. | Page (Some pseudo, decls) ->
  133. "@page :" ^ pseudo ^ block (cat "\n" string_of_declaration decls)
  134. | Font_face decls ->
  135. "@font-face" ^ block (cat "\n" string_of_descriptor_declaration decls)
  136. | Namespace (None, uri) ->
  137. "@namespace " ^ string_of_expr uri ^ ";"
  138. | Namespace (Some prefix, uri) ->
  139. "@namespace " ^ prefix ^ " " ^ string_of_expr uri ^ ";"
  140. | Keyframes (prefix, id, rules) ->
  141. "@" ^ prefix ^ "keyframes " ^ id ^
  142. block (cat "\n\n" string_of_keyframe_ruleset rules)
  143. | Supports (condition, statements) ->
  144. "@supports " ^ string_of_condition condition ^
  145. block (cat "\n\n" string_of_statement statements)
  146. | Viewport (prefix, decls) ->
  147. "@" ^ prefix ^ "viewport" ^ block (cat "\n" string_of_declaration decls)
  148. let string_of_stylesheet = cat "\n\n" string_of_statement
  149. (*
  150. * Minified stringification
  151. *)
  152. let minify_num n =
  153. (* Round numbers to at most 2 decimal digits *)
  154. let round2 n = floor (100. *. n +. 0.5) /. 100. in
  155. if float_of_int (int_of_float n) = n then
  156. string_of_int (int_of_float n)
  157. else if n < 1.0 && n > -1.0 then
  158. let s = string_of_float (round2 n) in
  159. String.sub s 1 (String.length s - 1)
  160. else
  161. string_of_float (round2 n)
  162. let rec minify_expr = function
  163. | Concat values -> cat " " minify_expr values
  164. | Function (name, arg) -> name ^ "(" ^ minify_expr arg ^ ")"
  165. | Unary (op, opnd) -> op ^ minify_expr opnd
  166. | Nary (",", opnds) -> cat "," minify_expr opnds
  167. | Nary (op, opnds) -> cat op minify_expr opnds
  168. | Number (n, None) -> minify_num n
  169. | Number (n, Some u) -> minify_num n ^ u
  170. | Key_value (key, op, value) -> key ^ op ^ minify_expr value
  171. | expr -> string_of_expr expr
  172. let minify_declaration (name, value, important) =
  173. let imp = if important then "!important" else "" in
  174. name ^ ":" ^ minify_expr value ^ imp
  175. let rec minify_selector = stringify_selector ""
  176. let minify_media_feature = function
  177. | (feature, None) -> "(" ^ feature ^ ")"
  178. | (feature, Some value) -> "(" ^ feature ^ ":" ^ minify_expr value ^ ")"
  179. let minify_media_query query =
  180. let features_str = cat "and " minify_media_feature in
  181. match query with
  182. | (None, None, features) -> features_str features
  183. | (None, Some mtype, features) -> mtype ^ " and " ^ features_str features
  184. | (Some pre, Some mtype, features) ->
  185. pre ^ " " ^ mtype ^ " and " ^ features_str features
  186. | _ -> string_of_media_query query
  187. let rec minify_statement = function
  188. | Ruleset (selectors, decls) ->
  189. cat "," minify_selector selectors ^
  190. "{" ^ cat ";" minify_declaration decls ^ "}"
  191. | Media (queries, rulesets) ->
  192. "@media" ^ prefix_space (cat "," minify_media_query queries) ^
  193. "{" ^ cat "" minify_statement rulesets ^ "}"
  194. | Import (target, []) ->
  195. "@import " ^ minify_expr target ^ ";"
  196. | Import (target, queries) ->
  197. "@import " ^ minify_expr target ^ " " ^
  198. cat "," string_of_media_query queries ^ ";"
  199. | Page (None, decls) ->
  200. "@page{" ^ cat ";" minify_declaration decls ^ "}"
  201. | Page (Some pseudo, decls) ->
  202. "@page :" ^ pseudo ^ "{" ^ cat ";" minify_declaration decls ^ "}"
  203. | Font_face decls ->
  204. let minify_descriptor_declaration (name, value) =
  205. name ^ ":" ^ minify_expr value
  206. in
  207. "@font-face{" ^ cat ";" minify_descriptor_declaration decls ^ "}"
  208. | Keyframes (prefix, id, rules) ->
  209. let minify_keyframe_ruleset (expr, decls) =
  210. minify_expr expr ^ "{" ^ cat ";" minify_declaration decls ^ "}"
  211. in
  212. "@" ^ prefix ^ "keyframes " ^ id ^
  213. "{" ^ cat "" minify_keyframe_ruleset rules ^ "}"
  214. | Supports (condition, statements) ->
  215. "@supports " ^ stringify_condition "" condition ^
  216. "{" ^ cat "" minify_statement statements ^ "}"
  217. | Viewport (prefix, decls) ->
  218. "@" ^ prefix ^ "viewport{" ^ cat ";" minify_declaration decls ^ "}"
  219. | statement -> string_of_statement statement
  220. let minify_stylesheet = cat "" minify_statement
  221. (*
  222. * Stringify any AST node in a box
  223. *)
  224. let string_of_box = function
  225. | Expr expr ->
  226. string_of_expr expr
  227. | Declaration declaration ->
  228. string_of_declaration declaration
  229. | Selector selector ->
  230. string_of_selector selector
  231. | Media_expr media_expr ->
  232. string_of_media_expr media_expr
  233. | Media_query media_query ->
  234. string_of_media_query media_query
  235. | Descriptor_declaration descriptor_declaration ->
  236. string_of_descriptor_declaration descriptor_declaration
  237. | Keyframe_ruleset keyframe_ruleset ->
  238. string_of_keyframe_ruleset keyframe_ruleset
  239. | Condition condition ->
  240. string_of_condition condition
  241. | Statement statement ->
  242. string_of_statement statement
  243. | Stylesheet stylesheet ->
  244. string_of_stylesheet stylesheet
  245. | Clear ->
  246. "<clear>"
  247. | _ ->
  248. raise (Invalid_argument "box")