class Roadie::Inliner

@api private The Inliner inlines stylesheets to the elements of the DOM.

Inlining means that {StyleBlock}s and a DOM tree are combined:

a { color: red; } # StyleBlock
<a href="/"></a>  # DOM

becomes

<a href="/" style="color:red"></a>

Attributes

dom[R]
stylesheets[R]

Public Class Methods

new(stylesheets, dom) click to toggle source

@param [Array<Stylesheet>] stylesheets the stylesheets to use in the inlining @param [Nokogiri::HTML::Document] dom

# File lib/roadie/inliner.rb, line 20
def initialize(stylesheets, dom)
  @stylesheets = stylesheets
  @dom = dom
end

Public Instance Methods

inline(keep_extra_blocks = true) click to toggle source

Start the inlining, mutating the DOM tree.

@param [true, false] keep_extra_blocks @return [nil]

# File lib/roadie/inliner.rb, line 29
def inline(keep_extra_blocks = true)
  style_map, extra_blocks = consume_stylesheets

  apply_style_map(style_map)
  add_styles_to_head(extra_blocks) if keep_extra_blocks

  nil
end

Private Instance Methods

add_styles_to_head(blocks) click to toggle source
# File lib/roadie/inliner.rb, line 92
def add_styles_to_head(blocks)
  unless blocks.empty?
    create_style_element(blocks, find_head)
  end
end
apply_element_style(element, builder) click to toggle source
# File lib/roadie/inliner.rb, line 73
def apply_element_style(element, builder)
  element["style"] = [builder.attribute_string, element["style"]].compact.join(";")
end
apply_style_map(style_map) click to toggle source
# File lib/roadie/inliner.rb, line 69
def apply_style_map(style_map)
  style_map.each_element { |element, builder| apply_element_style(element, builder) }
end
consume_stylesheets() click to toggle source
# File lib/roadie/inliner.rb, line 42
def consume_stylesheets
  style_map = StyleMap.new
  extra_blocks = []

  each_style_block do |stylesheet, block|
    if (elements = selector_elements(stylesheet, block))
      style_map.add elements, block.properties
    else
      extra_blocks << block
    end
  end

  [style_map, extra_blocks]
end
create_style_element(style_blocks, head) click to toggle source
# File lib/roadie/inliner.rb, line 102
def create_style_element(style_blocks, head)
  return unless head
  element = Nokogiri::XML::Node.new("style", head.document)
  element.content = style_blocks.join("\n")
  head.add_child(element)
end
each_style_block() { |stylesheet, block| ... } click to toggle source
# File lib/roadie/inliner.rb, line 57
def each_style_block
  stylesheets.each do |stylesheet|
    stylesheet.blocks.each do |block|
      yield stylesheet, block
    end
  end
end
elements_matching_selector(stylesheet, selector) click to toggle source
# File lib/roadie/inliner.rb, line 77
def elements_matching_selector(stylesheet, selector)
  dom.css(selector.to_s)
# There's no way to get a list of supported pseudo selectors, so we're left
# with having to rescue errors.
# Pseudo selectors that are known to be bad are skipped automatically but
# this will catch the rest.
rescue Nokogiri::XML::XPath::SyntaxError, Nokogiri::CSS::SyntaxError => error
  Utils.warn "Cannot inline #{selector.inspect} from \"#{stylesheet.name}\" stylesheet. If this is valid CSS, please report a bug."
  nil
rescue => error
  Utils.warn "Got error when looking for #{selector.inspect} (from \"#{stylesheet.name}\" stylesheet): #{error}"
  raise unless error.message.include?('XPath')
  nil
end
find_head() click to toggle source
# File lib/roadie/inliner.rb, line 98
def find_head
  dom.at_xpath('html/head')
end
selector_elements(stylesheet, block) click to toggle source
# File lib/roadie/inliner.rb, line 65
def selector_elements(stylesheet, block)
  block.inlinable? && elements_matching_selector(stylesheet, block.selector)
end