css.rb 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390
  1. require File.dirname(__FILE__) + '/../sass'
  2. require 'sass/tree/node'
  3. require 'sass/scss/css_parser'
  4. module Sass
  5. # This class converts CSS documents into Sass or SCSS templates.
  6. # It works by parsing the CSS document into a {Sass::Tree} structure,
  7. # and then applying various transformations to the structure
  8. # to produce more concise and idiomatic Sass/SCSS.
  9. #
  10. # Example usage:
  11. #
  12. # Sass::CSS.new("p { color: blue }").render(:sass) #=> "p\n color: blue"
  13. # Sass::CSS.new("p { color: blue }").render(:scss) #=> "p {\n color: blue; }"
  14. class CSS
  15. # @param template [String] The CSS stylesheet.
  16. # This stylesheet can be encoded using any encoding
  17. # that can be converted to Unicode.
  18. # If the stylesheet contains an `@charset` declaration,
  19. # that overrides the Ruby encoding
  20. # (see {file:SASS_REFERENCE.md#encodings the encoding documentation})
  21. # @option options :old [Boolean] (false)
  22. # Whether or not to output old property syntax
  23. # (`:color blue` as opposed to `color: blue`).
  24. # This is only meaningful when generating Sass code,
  25. # rather than SCSS.
  26. def initialize(template, options = {})
  27. if template.is_a? IO
  28. template = template.read
  29. end
  30. @options = options.dup
  31. # Backwards compatibility
  32. @options[:old] = true if @options[:alternate] == false
  33. @template = template
  34. end
  35. # Converts the CSS template into Sass or SCSS code.
  36. #
  37. # @param fmt [Symbol] `:sass` or `:scss`, designating the format to return.
  38. # @return [String] The resulting Sass or SCSS code
  39. # @raise [Sass::SyntaxError] if there's an error parsing the CSS template
  40. def render(fmt = :sass)
  41. check_encoding!
  42. build_tree.send("to_#{fmt}", @options).strip + "\n"
  43. rescue Sass::SyntaxError => err
  44. err.modify_backtrace(:filename => @options[:filename] || '(css)')
  45. raise err
  46. end
  47. # Returns the original encoding of the document,
  48. # or `nil` under Ruby 1.8.
  49. #
  50. # @return [Encoding, nil]
  51. # @raise [Encoding::UndefinedConversionError] if the source encoding
  52. # cannot be converted to UTF-8
  53. # @raise [ArgumentError] if the document uses an unknown encoding with `@charset`
  54. def source_encoding
  55. check_encoding!
  56. @original_encoding
  57. end
  58. private
  59. def check_encoding!
  60. return if @checked_encoding
  61. @checked_encoding = true
  62. @template, @original_encoding = Sass::Util.check_sass_encoding(@template) do |msg, line|
  63. raise Sass::SyntaxError.new(msg, :line => line)
  64. end
  65. end
  66. # Parses the CSS template and applies various transformations
  67. #
  68. # @return [Tree::Node] The root node of the parsed tree
  69. def build_tree
  70. root = Sass::SCSS::CssParser.new(@template, @options[:filename]).parse
  71. parse_selectors root
  72. expand_commas root
  73. nest_seqs root
  74. parent_ref_rules root
  75. flatten_rules root
  76. fold_commas root
  77. dump_selectors root
  78. root
  79. end
  80. # Parse all the selectors in the document and assign them to
  81. # {Sass::Tree::RuleNode#parsed_rules}.
  82. #
  83. # @param root [Tree::Node] The parent node
  84. def parse_selectors(root)
  85. root.children.each do |child|
  86. next parse_selectors(child) if child.is_a?(Tree::DirectiveNode)
  87. next unless child.is_a?(Tree::RuleNode)
  88. parser = Sass::SCSS::CssParser.new(child.rule.first, child.filename, child.line)
  89. child.parsed_rules = parser.parse_selector
  90. end
  91. end
  92. # Transform
  93. #
  94. # foo, bar, baz
  95. # color: blue
  96. #
  97. # into
  98. #
  99. # foo
  100. # color: blue
  101. # bar
  102. # color: blue
  103. # baz
  104. # color: blue
  105. #
  106. # @param root [Tree::Node] The parent node
  107. def expand_commas(root)
  108. root.children.map! do |child|
  109. # child.parsed_rules.members.size > 1 iff the rule contains a comma
  110. unless child.is_a?(Tree::RuleNode) && child.parsed_rules.members.size > 1
  111. expand_commas(child) if child.is_a?(Tree::DirectiveNode)
  112. next child
  113. end
  114. child.parsed_rules.members.map do |seq|
  115. node = Tree::RuleNode.new([])
  116. node.parsed_rules = make_cseq(seq)
  117. node.children = child.children
  118. node
  119. end
  120. end
  121. root.children.flatten!
  122. end
  123. # Make rules use nesting so that
  124. #
  125. # foo
  126. # color: green
  127. # foo bar
  128. # color: red
  129. # foo baz
  130. # color: blue
  131. #
  132. # becomes
  133. #
  134. # foo
  135. # color: green
  136. # bar
  137. # color: red
  138. # baz
  139. # color: blue
  140. #
  141. # @param root [Tree::Node] The parent node
  142. def nest_seqs(root)
  143. current_rule = nil
  144. root.children.map! do |child|
  145. unless child.is_a?(Tree::RuleNode)
  146. nest_seqs(child) if child.is_a?(Tree::DirectiveNode)
  147. next child
  148. end
  149. seq = first_seq(child)
  150. seq.members.reject! {|sseq| sseq == "\n"}
  151. first, rest = seq.members.first, seq.members[1..-1]
  152. if current_rule.nil? || first_sseq(current_rule) != first
  153. current_rule = Tree::RuleNode.new([])
  154. current_rule.parsed_rules = make_seq(first)
  155. end
  156. unless rest.empty?
  157. child.parsed_rules = make_seq(*rest)
  158. current_rule << child
  159. else
  160. current_rule.children += child.children
  161. end
  162. current_rule
  163. end
  164. root.children.compact!
  165. root.children.uniq!
  166. root.children.each {|v| nest_seqs(v)}
  167. end
  168. # Make rules use parent refs so that
  169. #
  170. # foo
  171. # color: green
  172. # foo.bar
  173. # color: blue
  174. #
  175. # becomes
  176. #
  177. # foo
  178. # color: green
  179. # &.bar
  180. # color: blue
  181. #
  182. # @param root [Tree::Node] The parent node
  183. def parent_ref_rules(root)
  184. current_rule = nil
  185. root.children.map! do |child|
  186. unless child.is_a?(Tree::RuleNode)
  187. parent_ref_rules(child) if child.is_a?(Tree::DirectiveNode)
  188. next child
  189. end
  190. sseq = first_sseq(child)
  191. next child unless sseq.is_a?(Sass::Selector::SimpleSequence)
  192. firsts, rest = [sseq.members.first], sseq.members[1..-1]
  193. firsts.push rest.shift if firsts.first.is_a?(Sass::Selector::Parent)
  194. if current_rule.nil? || first_sseq(current_rule).members != firsts
  195. current_rule = Tree::RuleNode.new([])
  196. current_rule.parsed_rules = make_sseq(*firsts)
  197. end
  198. unless rest.empty?
  199. rest.unshift Sass::Selector::Parent.new
  200. child.parsed_rules = make_sseq(*rest)
  201. current_rule << child
  202. else
  203. current_rule.children += child.children
  204. end
  205. current_rule
  206. end
  207. root.children.compact!
  208. root.children.uniq!
  209. root.children.each {|v| parent_ref_rules(v)}
  210. end
  211. # Flatten rules so that
  212. #
  213. # foo
  214. # bar
  215. # color: red
  216. #
  217. # becomes
  218. #
  219. # foo bar
  220. # color: red
  221. #
  222. # and
  223. #
  224. # foo
  225. # &.bar
  226. # color: blue
  227. #
  228. # becomes
  229. #
  230. # foo.bar
  231. # color: blue
  232. #
  233. # @param root [Tree::Node] The parent node
  234. def flatten_rules(root)
  235. root.children.each do |child|
  236. case child
  237. when Tree::RuleNode
  238. flatten_rule(child)
  239. when Tree::DirectiveNode
  240. flatten_rules(child)
  241. end
  242. end
  243. end
  244. # Flattens a single rule.
  245. #
  246. # @param rule [Tree::RuleNode] The candidate for flattening
  247. # @see #flatten_rules
  248. def flatten_rule(rule)
  249. while rule.children.size == 1 && rule.children.first.is_a?(Tree::RuleNode)
  250. child = rule.children.first
  251. if first_simple_sel(child).is_a?(Sass::Selector::Parent)
  252. rule.parsed_rules = child.parsed_rules.resolve_parent_refs(rule.parsed_rules)
  253. else
  254. rule.parsed_rules = make_seq(first_sseq(rule), *first_seq(child).members)
  255. end
  256. rule.children = child.children
  257. end
  258. flatten_rules(rule)
  259. end
  260. # Transform
  261. #
  262. # foo
  263. # bar
  264. # color: blue
  265. # baz
  266. # color: blue
  267. #
  268. # into
  269. #
  270. # foo
  271. # bar, baz
  272. # color: blue
  273. #
  274. # @param rule [Tree::RuleNode] The candidate for flattening
  275. def fold_commas(root)
  276. prev_rule = nil
  277. root.children.map! do |child|
  278. unless child.is_a?(Tree::RuleNode)
  279. fold_commas(child) if child.is_a?(Tree::DirectiveNode)
  280. next child
  281. end
  282. if prev_rule && prev_rule.children == child.children
  283. prev_rule.parsed_rules.members << first_seq(child)
  284. next nil
  285. end
  286. fold_commas(child)
  287. prev_rule = child
  288. child
  289. end
  290. root.children.compact!
  291. end
  292. # Dump all the parsed {Sass::Tree::RuleNode} selectors to strings.
  293. #
  294. # @param root [Tree::Node] The parent node
  295. def dump_selectors(root)
  296. root.children.each do |child|
  297. next dump_selectors(child) if child.is_a?(Tree::DirectiveNode)
  298. next unless child.is_a?(Tree::RuleNode)
  299. child.rule = [child.parsed_rules.to_s]
  300. dump_selectors(child)
  301. end
  302. end
  303. # Create a {Sass::Selector::CommaSequence}.
  304. #
  305. # @param seqs [Array<Sass::Selector::Sequence>]
  306. # @return [Sass::Selector::CommaSequence]
  307. def make_cseq(*seqs)
  308. Sass::Selector::CommaSequence.new(seqs)
  309. end
  310. # Create a {Sass::Selector::CommaSequence} containing only a single
  311. # {Sass::Selector::Sequence}.
  312. #
  313. # @param sseqs [Array<Sass::Selector::Sequence, String>]
  314. # @return [Sass::Selector::CommaSequence]
  315. def make_seq(*sseqs)
  316. make_cseq(Sass::Selector::Sequence.new(sseqs))
  317. end
  318. # Create a {Sass::Selector::CommaSequence} containing only a single
  319. # {Sass::Selector::Sequence} which in turn contains only a single
  320. # {Sass::Selector::SimpleSequence}.
  321. #
  322. # @param sseqs [Array<Sass::Selector::Sequence, String>]
  323. # @return [Sass::Selector::CommaSequence]
  324. def make_sseq(*sseqs)
  325. make_seq(Sass::Selector::SimpleSequence.new(sseqs))
  326. end
  327. # Return the first {Sass::Selector::Sequence} in a {Sass::Tree::RuleNode}.
  328. #
  329. # @param rule [Sass::Tree::RuleNode]
  330. # @return [Sass::Selector::Sequence]
  331. def first_seq(rule)
  332. rule.parsed_rules.members.first
  333. end
  334. # Return the first {Sass::Selector::SimpleSequence} in a
  335. # {Sass::Tree::RuleNode}.
  336. #
  337. # @param rule [Sass::Tree::RuleNode]
  338. # @return [Sass::Selector::SimpleSequence, String]
  339. def first_sseq(rule)
  340. first_seq(rule).members.first
  341. end
  342. # Return the first {Sass::Selector::Simple} in a {Sass::Tree::RuleNode},
  343. # unless the rule begins with a combinator.
  344. #
  345. # @param rule [Sass::Tree::RuleNode]
  346. # @return [Sass::Selector::Simple?]
  347. def first_simple_sel(rule)
  348. sseq = first_sseq(rule)
  349. return unless sseq.is_a?(Sass::Selector::SimpleSequence)
  350. sseq.members.first
  351. end
  352. end
  353. end