class Nanoc::Filters::RelativizePaths

@api private

Constants

GCSE_SEARCH_WORKAROUND
SELECTORS

Public Instance Methods

run(content, params = {}) click to toggle source

Relativizes all paths in the given content, which can be HTML, XHTML, XML or CSS. This filter is quite useful if a site needs to be hosted in a subdirectory instead of a subdomain. In HTML, all ‘href` and `src` attributes will be relativized. In CSS, all `url()` references will be relativized.

@param [String] content The content to filter

@option params [Symbol] :type The type of content to filter; can be

`:html`, `:xhtml`, `:xml` or `:css`.

@option params [Array] :select The XPath expressions that matches the

nodes to modify. This param is useful only for the `:html`, `:xml` and
`:xhtml` types.

@option params [Hash] :namespaces The pairs ‘prefix => uri` to define

any namespace you want to use in the XPath expressions. This param
is useful only for the `:xml` and `:xhtml` types.

@return [String] The filtered content

# File lib/nanoc/filters/relativize_paths.rb, line 47
def run(content, params = {})
  # Set assigns so helper function can be used
  @item_rep = assigns[:item_rep] if @item_rep.nil?

  # Filter
  case params[:type]
  when :css
    relativize_css(content, params)
  when :html, :html5, :xml, :xhtml
    relativize_html_like(content, params)
  else
    raise 'The relativize_paths needs to know the type of content to ' \
      'process. Pass a :type to the filter call (:html for HTML, ' \
      ':xhtml for XHTML, :xml for XML, or :css for CSS).'
  end
end

Protected Instance Methods

apply_gcse_search_workaround(content) click to toggle source
# File lib/nanoc/filters/relativize_paths.rb, line 160
def apply_gcse_search_workaround(content)
  content.gsub('gcse:search', GCSE_SEARCH_WORKAROUND)
end
exclude?(path, params) click to toggle source
# File lib/nanoc/filters/relativize_paths.rb, line 93
def exclude?(path, params)
  # TODO: Use #match? on newer Ruby versions
  excludes(params).any? { |ex| path =~ ex }
end
excludes(params) click to toggle source
# File lib/nanoc/filters/relativize_paths.rb, line 80
def excludes(params)
  raw = [params.fetch(:exclude, [])].flatten
  raw.map do |exclusion|
    case exclusion
    when Regexp
      exclusion
    when String
      /\A#{exclusion}(\z|\/)/
    end
  end
end
fix_content(content, type) click to toggle source
# File lib/nanoc/filters/relativize_paths.rb, line 127
def fix_content(content, type)
  case type
  when :xhtml
    # FIXME: cleanup because it is ugly
    # this cleans the XHTML namespace to process fragments and full
    # documents in the same way. At least, Nokogiri adds this namespace
    # if detects the `html` element.
    content.sub(%r{(<html[^>]+)xmlns="http://www.w3.org/1999/xhtml"}, '\1')
  else
    content
  end
end
handle_selectors(selectors, doc, namespaces, klass, type, params) click to toggle source
# File lib/nanoc/filters/relativize_paths.rb, line 168
def handle_selectors(selectors, doc, namespaces, klass, type, params)
  selectors_by_type(selectors).each do |selector_type, sub_selectors|
    selector = sub_selectors.map { |sel| "descendant-or-self::#{sel.fetch(:path)}" }.join('|')

    doc.xpath(selector, namespaces).each do |node|
      if node.name == 'comment'
        nokogiri_process_comment(node, doc, sub_selectors, namespaces, klass, type, params)
      elsif path_is_relativizable?(node.content, params)
        node.content = relativize_node(node, selector_type)
      end
    end
  end
end
handle_srcset_node(node) click to toggle source
# File lib/nanoc/filters/relativize_paths.rb, line 218
def handle_srcset_node(node)
  parsed = Nanoc::Extra::SrcsetParser.new(node.content).call

  if parsed.is_a?(Array)
    parsed.map do |pair|
      [relative_path_to(pair[:url]), pair[:rest]].join('')
    end.join(',')
  else
    relative_path_to(parsed)
  end
end
nokogiri_process(content, selectors, namespaces, klass, type, nokogiri_save_options, params) click to toggle source
# File lib/nanoc/filters/relativize_paths.rb, line 140
def nokogiri_process(content, selectors, namespaces, klass, type, nokogiri_save_options, params)
  # Ensure that all prefixes are strings
  namespaces = namespaces.reduce({}) { |new, (prefix, uri)| new.merge(prefix.to_s => uri) }

  content = apply_gcse_search_workaround(content)

  doc = /<html[\s>]/.match?(content) ? klass.parse(content) : klass.fragment(content)
  handle_selectors(selectors, doc, namespaces, klass, type, params)

  output =
    case type
    when :html5
      doc.to_html(save_with: nokogiri_save_options)
    else
      doc.send("to_#{type}", save_with: nokogiri_save_options)
    end

  revert_gcse_search_workaround(output)
end
nokogiri_process_comment(node, doc, selectors, namespaces, klass, type, params) click to toggle source
# File lib/nanoc/filters/relativize_paths.rb, line 195
def nokogiri_process_comment(node, doc, selectors, namespaces, klass, type, params)
  content = node.content.dup.sub(%r{^(\s*\[.+?\]>\s*)(.+?)(\s*<!\[endif\])}m) do |_m|
    beginning = Regexp.last_match[1]
    body = Regexp.last_match[2]
    ending = Regexp.last_match[3]

    beginning + nokogiri_process(body, selectors, namespaces, klass, type, nil, params) + ending
  end

  node.replace(Nokogiri::XML::Comment.new(doc, content))
end
parser_for(type) click to toggle source
# File lib/nanoc/filters/relativize_paths.rb, line 110
def parser_for(type)
  case type
  when :html
    require 'nokogiri'
    ::Nokogiri::HTML
  when :html5
    require 'nokogiri'
    ::Nokogiri::HTML5
  when :xml
    require 'nokogiri'
    ::Nokogiri::XML
  when :xhtml
    require 'nokogiri'
    ::Nokogiri::XML
  end
end
path_is_relativizable?(path, params) click to toggle source
# File lib/nanoc/filters/relativize_paths.rb, line 230
def path_is_relativizable?(path, params)
  path.match?(/\A\s*\//) && !exclude?(path, params)
end
relativize_css(content, params) click to toggle source
# File lib/nanoc/filters/relativize_paths.rb, line 66
def relativize_css(content, params)
  # FIXME: parse CSS the proper way using csspool or something
  content.gsub(/url\((['"]?)(\/(?:[^\/].*?)?)\1\)/) do
    quote = Regexp.last_match[1]
    path = Regexp.last_match[2]

    if exclude?(path, params)
      Regexp.last_match[0]
    else
      'url(' + quote + relative_path_to(path) + quote + ')'
    end
  end
end
relativize_html_like(content, params) click to toggle source
# File lib/nanoc/filters/relativize_paths.rb, line 98
def relativize_html_like(content, params)
  selectors             = params.fetch(:select, SELECTORS)
  namespaces            = params.fetch(:namespaces, {})
  type                  = params.fetch(:type)
  nokogiri_save_options = params.fetch(:nokogiri_save_options, nil)

  parser = parser_for(type)
  content = fix_content(content, type)

  nokogiri_process(content, selectors, namespaces, parser, type, nokogiri_save_options, params)
end
relativize_node(node, selector_type) click to toggle source
# File lib/nanoc/filters/relativize_paths.rb, line 207
def relativize_node(node, selector_type)
  case selector_type
  when :basic
    relative_path_to(node.content)
  when :srcset
    handle_srcset_node(node)
  else
    raise Nanoc::Core::Errors::InternalInconsistency, "Unsupported selector type #{selector_type.inspect} in #{self.class}"
  end
end
revert_gcse_search_workaround(content) click to toggle source
# File lib/nanoc/filters/relativize_paths.rb, line 164
def revert_gcse_search_workaround(content)
  content.gsub(GCSE_SEARCH_WORKAROUND, 'gcse:search')
end
selectors_by_type(selectors) click to toggle source
# File lib/nanoc/filters/relativize_paths.rb, line 182
def selectors_by_type(selectors)
  typed_selectors =
    selectors.map do |s|
      if s.respond_to?(:keys)
        s
      else
        { path: s, type: :basic }
      end
    end

  typed_selectors.group_by { |s| s.fetch(:type) }
end